This is my notebook that I will used to go through the tidy text notebook by Julia Silge and David Robinson.

library(tidyverse)
Warning message:
In as.POSIXlt.POSIXct(x, tz) : unknown timezone 'default/America/Detroit'
library(tidytext)
library(stringr)
library(janeaustenr)
text <- c("Because I could not stop for Death - ", 
          "He kindly stopped for me -",
          "The Carriage held but just Ourselves -",
          "and Immortality")
text
[1] "Because I could not stop for Death - " 
[2] "He kindly stopped for me -"            
[3] "The Carriage held but just Ourselves -"
[4] "and Immortality"                       
text_df <- data_frame(line = 1:4, text = text)
text_df
text_df %>% 
  unnest_tokens(word, text)  # to_lower = FALSE to keep uppercase

Working with Jane Austen Books

Using the Jane Austen dataset we can make it tidy. First we’ll use mutate to create a column from existing data. Annotate line numbers and then keep track of chapters using regex.

original_books <- austen_books() %>% 
  group_by(book) %>% 
  mutate(linenumber = row_number(),
         chapter = cumsum(str_detect(text, regex("^chapter [\\divxlc]",
                                                  ignore.case = TRUE)))) %>% 
  ungroup()
original_books

Restructure it in one-token-per-row format with the unnest_tokens() function.

tidy_books <- original_books %>% 
  unnest_tokens(word, text)
tidy_books

Removing stop words

We can remove stop words

data(stop_words)
tidy_books <- tidy_books %>% 
  anti_join(stop_words)
Joining, by = "word"
tidy_books %>% 
  count(word, sort = TRUE)

Plotting data

tidy_books %>% 
  count(word, sort = TRUE) %>% 
  filter(n > 600) %>% 
  mutate(word = reorder(word, n)) %>% 
  ggplot(aes(word, n))+
  geom_col()+
  xlab(NULL)+
  coord_flip()+
  theme_bw()

Gutenbergr

HG wells books

library(gutenbergr)
hgwells <-  gutenberg_download(c(35, 36, 5230, 159))
Determining mirror for Project Gutenberg from http://www.gutenberg.org/robot/harvest
Using mirror http://aleph.gutenberg.org
tidy_hgwells <- hgwells %>% 
  unnest_tokens(word, text) %>% 
  anti_join(stop_words)
Joining, by = "word"
tidy_hgwells %>% 
  count(word, sort = TRUE)

Most commone words in novels by Bronte sisters

bronte <-  gutenberg_download(c(1260, 768, 969, 9182, 767))
tidy_bronte <- bronte %>% 
  unnest_tokens(word, text) %>% 
  anti_join(stop_words)
Joining, by = "word"
tidy_bronte %>% 
  count(word, sort = TRUE)

Comparing Bronte and HG Wells

frequency <- bind_rows(mutate(tidy_bronte, author = "Bronte Sisters"),
                       mutate(tidy_hgwells, author = "H.G. Wells"),
                       mutate(tidy_books, author = "Jane Austen")) %>% 
  mutate(word = str_extract(word, "[a-z']+")) %>% 
  count(author, word) %>% 
  group_by(author) %>% 
  mutate(proportion = n / sum(n)) %>% 
  select(-n) %>% 
  spread(author, proportion) %>% 
  gather(author, proportion, `Bronte Sisters`:`H.G. Wells`)

Graphing the comparisons

library(scales)

Attaching package: ‘scales’

The following object is masked from ‘package:purrr’:

    discard

The following object is masked from ‘package:readr’:

    col_factor
ggplot(frequency, aes(x = proportion, y = `Jane Austen`,
                      color = abs(`Jane Austen` - proportion))) +
  geom_abline(color = "gray40", lty = 2) +
  geom_jitter(alpha = 0.1, size = 2.5, width = 0.3, height = 0.3) +
  geom_text(aes(label = word, check_overlap = TRUE, vjust = 1.5))+
  scale_x_log10(labels = percent_format()) +
  scale_y_log10(labels = percent_format()) +
  scale_color_gradient(limits = c(0,0.001),
                       low = "darkslategray4", high = "gray75") +
  facet_wrap(~author, ncol = 2) +
  theme(legend.position = "none") +
  labs(y = "Jane Austen", x = NULL)
Ignoring unknown aesthetics: check_overlap

NA

Correlation analysis

cor.test(data = frequency[frequency$author == "Bronte Sisters",],
         ~ proportion + `Jane Austen`)

    Pearson's product-moment correlation

data:  proportion and Jane Austen
t = 119.65, df = 10404, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.7527846 0.7689620
sample estimates:
      cor 
0.7609915 
cor.test(data = frequency[frequency$author == "H.G. Wells",],
         ~ proportion + `Jane Austen`)

    Pearson's product-moment correlation

data:  proportion and Jane Austen
t = 36.441, df = 6053, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.4032800 0.4445987
sample estimates:
      cor 
0.4241601 

Sentiments dataset

library(tidytext)
sentiments

Sentiment scores

# AFINN
get_sentiments("afinn")
# Bing
get_sentiments("bing")
# NRC
get_sentiments("nrc")

Sentiment analysis can be performed with an inner join

tidy_books <- austen_books() %>% 
  group_by(book) %>% 
  mutate(linenumber = row_number(),
         chapter = cumsum(str_detect(text, regex("^chapter [\\divxlc]",
                                                 ignore_case = TRUE)))) %>% 
  ungroup() %>% 
  unnest_tokens(word, text)
tidy_books

Get a list of words that are associated with ‘joy’ and perform an inner_join to find words that are have ‘joy’ sentiment within Jane Austen’s Emma book.

# Filter words associated with 'joy'
nrcjoy <- get_sentiments("nrc") %>% 
  filter(sentiment == "joy")
# Find words associated with joy in Emma
tidy_books %>% 
  filter(book == "Emma") %>% 
  inner_join(nrcjoy) %>% 
  count(word, sort = TRUE)
Joining, by = "word"

Count up positive or negative words that are within sections of each book. We will define an index to keep track of where we are in the narrative. The index will count up sections of 80 lines of text

janeaustensentiment <- tidy_books %>% 
  inner_join(get_sentiments("bing")) %>% 
  count(book, index = linenumber %/% 80, sentiment) %>% 
  spread(sentiment, n, fill =0) %>% 
  mutate(sentiment = positive - negative)
Joining, by = "word"
janeaustensentiment
ggplot(janeaustensentiment, aes(index, sentiment, fill = book)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~book, ncol = 2, scales = "free_x")

pride_prejudice <- tidy_books %>% 
  filter(book == "Pride & Prejudice")
pride_prejudice
afinn <- pride_prejudice %>% 
  inner_join(get_sentiments("afinn")) %>% 
  group_by(index = linenumber %/% 80) %>% 
  summarise(sentiment = sum(score)) %>% 
  mutate(method = "AFINN")
Joining, by = "word"
afinn
bing_and_nrc <- bind_rows(
  pride_prejudice %>% 
    inner_join(get_sentiments("bing")) %>% 
    mutate(method = "Bing et al."),
  pride_prejudice %>% 
    inner_join(get_sentiments("nrc") %>% 
                 filter(sentiment %in% c("positive",
                                         "negative"))) %>% 
    mutate(method = "NRC")) %>% 
    count(method, index = linenumber %/% 80, sentiment) %>% 
    spread(sentiment, n, fill = 0) %>% 
    mutate(sentiment = positive - negative)
Joining, by = "word"
Joining, by = "word"
bing_and_nrc

Comparing three sentiment lexicons using Pride and Prejudice

bind_rows(afinn,
          bing_and_nrc) %>% 
  ggplot(aes(index, sentiment, fill = method)) +
    geom_col(show.legend = FALSE) +
    facet_wrap(~method, ncol = 1 , scales = "free_y")

get_sentiments("nrc") %>% 
  filter(sentiment %in% c("positive",
                          "negative")) %>% 
  count(sentiment)
get_sentiments("bing") %>% 
  count(sentiment)
bing_words_counts <- tidy_books %>% 
  inner_join(get_sentiments("bing")) %>% 
  count(word, sentiment, sort = TRUE) %>% 
  ungroup()
Joining, by = "word"
bing_words_counts
bing_words_counts %>% 
  group_by(sentiment) %>% 
  top_n(10) %>% 
  ungroup() %>% 
  mutate(word = reorder(word, n)) %>% 
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE)+
  facet_wrap(~sentiment, scales = "free_y") +
  labs(y = "Contribution to sentiment",
       x = NULL,
       title = "Top 10 words by Sentiment for Pride and Prejudice") +
  coord_flip()
Selecting by n

Miss is referred to as a negative word but is not in Jane Austen’s works.

custom_stop_words <- bind_rows(data_frame(word = c("miss"),
                                          lexicon = c("custom")),
                               stop_words)
custom_stop_words

Wordclouds

library(wordcloud)
Loading required package: RColorBrewer
# https://rstudio-pubs-static.s3.amazonaws.com/104366_6b7878746c4444628eaed8894d617bba.html
ggColors <- function(n) {
  hues = seq(15, 375, length=n+1)
  hcl(h=hues, l=65, c=100)[1:n]
}
gg.cols <- ggColors(8)
tidy_books %>% 
  anti_join(stop_words) %>% 
  count(word) %>% 
  with(wordcloud(word, n, max.words = 100, random.color=TRUE, 
          colors=gg.cols))
Joining, by = "word"

library(reshape2)

Attaching package: ‘reshape2’

The following object is masked from ‘package:tidyr’:

    smiths
tidy_books %>% 
  inner_join(get_sentiments("bing")) %>% 
  count(word, sentiment, sort = TRUE) %>% 
  acast(word~sentiment, value.car = "n", fill = 0) %>% 
  comparison.cloud(colors = c("gray20", "gray80"),
                   max.words = 100)
Joining, by = "word"
Using n as value column: use value.var to override.

Looking at Units Beyond Just Words

PandP_sentences <-  data_frame(text = prideprejudice) %>% 
  unnest_tokens(sentence, text, token = "sentences")
PandP_sentences$sentence[2]
[1] "however little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered the rightful property of some one or other of their daughters."

Figuring out how many chapters are in a book

austen_chapters <- austen_books() %>% 
  group_by(book) %>% 
  unnest_tokens(chapter, text , token = "regex",
                pattern = "Chapter|CHAPTER [\\dIVXLC]") %>% 
  ungroup()
austen_chapters %>% 
  group_by(book) %>% 
  summarise(chapters = n())
bingnegative <- get_sentiments("bing") %>% 
  filter(sentiment == "negative")
wordcounts <- tidy_books %>% 
  group_by(book, chapter) %>% 
  summarize(words = n())
tidy_books %>% 
  semi_join(bingnegative) %>% 
  group_by(book, chapter) %>% 
  summarize(negativewords = n()) %>% 
  left_join(wordcounts, by = c("book", "chapter")) %>% 
  mutate(ratio = negativewords/words) %>% 
  filter(chapter !=0) %>% 
  top_n(1) %>% 
  ungroup()
Joining, by = "word"
Selecting by ratio

# Chapter 3

Term Frequency in Jane Austen’s Novels

book_words <- austen_books() %>% 
  unnest_tokens(word,text) %>% 
  count(book,word,sort = TRUE) %>% 
  ungroup()
total_words <- book_words %>% 
  group_by(book) %>% 
  summarize(total = sum(n))
book_words <- left_join(book_words, total_words)
Joining, by = "book"
book_words

Term frequency distribution in Jane Austen’s novels

ggplot(book_words, aes(n/total, fill = book)) +
  geom_histogram(show.legend = FALSE) +
  xlim(NA, 0.0009) +
  facet_wrap(~book, ncol = 2, scales = "free_y")

NA

Zipf’s Law

Zipf’s Law: frequency that a word appears is inversely proportional to its rank.

freq_by_rank <- book_words %>% 
  group_by(book) %>% 
  mutate(rank = row_number(),
         'term frequency' = n/total)
freq_by_rank

Graph of Zipf’s law for Jane Austen’s novels

freq_by_rank %>% 
  ggplot(aes(rank, `term frequency`, color = book)) +
  geom_line(size = 1.1, alpha = 0.8, show.legend = TRUE) +
  scale_x_log10() +
  scale_y_log10()

rank_subset <-  freq_by_rank %>% 
  filter(rank <500,
         rank >10)
lm(log10(`term frequency`) ~ log10(rank), data = rank_subset)

Call:
lm(formula = log10(`term frequency`) ~ log10(rank), data = rank_subset)

Coefficients:
(Intercept)  log10(rank)  
    -0.6226      -1.1125  
freq_by_rank %>% 
  ggplot(aes(rank, `term frequency`, color = book))+
  geom_abline(intercept = -0.62, slope = -1.1, color = "gray50", linetype = 2) +
  geom_line(size = 1.1, alpha = 0.8, show.legend = FALSE) +
  scale_x_log10() +
  scale_y_log10()

The bind_tf_idf Function

book_words <- book_words %>% 
  bind_tf_idf(word, book, n)
book_words
book_words %>% 
  select(-total) %>% 
  arrange(desc(tf_idf))
book_words %>% 
  arrange(desc(tf_idf)) %>% 
  mutate(word = factor(word, levels = rev(unique(word)))) %>% 
  group_by(book) %>% 
  top_n(15) %>% 
  ungroup() %>% 
  ggplot(aes(word, tf_idf, fill = book)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = "tf-idf") +
  facet_wrap(~book, ncol = 2,scales = "free") +
  coord_flip()
Selecting by tf_idf

A corpus of Physics Text

physics <- gutenberg_download(c(37729, 14725, 13476, 5001),
                              meta_fields = "author")
physics_words <- physics %>% 
  unnest_tokens(word, text) %>% 
  count(author, word, sort = TRUE) %>% 
  ungroup()
physics_words
author_order <- c("Galilei, Galileo", "Huygens, Christiaan", "Tesla, Nikola", "Einstein, Albert")
plot_physics <- physics_words %>% 
  bind_tf_idf(word, author, n) %>% 
  arrange(desc(tf_idf)) %>% 
  mutate(word = factor(word, levels = rev(unique(word)))) %>% 
  mutate(author = factor(author, levels = author_order))
plot_physics
plot_physics %>% 
  group_by(author) %>%
  top_n(15, tf_idf) %>% 
  ungroup() %>% 
  mutate(word = reorder(word, tf_idf)) %>% 
  ggplot(aes(word, tf_idf, fill = author)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = "tf-idf") +
  facet_wrap(~author, ncol = 2, scales = "free") +
  coord_flip()

NA

Isolating “eq” from Einstein’s text

physics %>% 
  filter(str_detect(text, "eq\\.")) %>% 
  select(text)

K1 was used for the coordinate system for Einstein

physics %>%
  filter(str_detect(text, "K1")) %>% 
  select(text)
NA
physics %>%
  filter(str_detect(text, "AK")) %>% 
  select(text)
mystopwords <- data_frame(word = c("eq","co","rc","ac","ak","bn",
                                   "fig", "file", "cg", "cb", "cm"))
physics_words <- anti_join(physics_words, mystopwords, by = "word")
plot_physics <- physics_words %>% 
  bind_tf_idf(word, author, n) %>% 
  arrange(desc(tf_idf)) %>% 
  mutate(word = factor(word, levels = rev(unique(word)))) %>% 
  group_by(author) %>% 
  top_n(15, tf_idf) %>% 
  ungroup() %>% 
  mutate(author = factor(author, levels = author_order))
plot_physics
ggplot(plot_physics, aes(word, tf_idf, fill = author)) +
  geom_col(show.legend = FALSE) +
  labs( x = NULL, y = "tf-idf") +
  facet_wrap(~author, ncol = 2, scales = "free") +
  coord_flip()

Chapter 4 Relationships Between Words: N-grams and Correlations

austen_bigrams <- austen_books() %>% 
  unnest_tokens(bigram, text, token = "ngrams", n = 2)
austen_bigrams

Counting and Filtering N-grams

austen_bigrams %>% 
  count(bigram, sort = TRUE)
bigrams_separated <-  austen_bigrams %>% 
  separate(bigram, into = c("word1", "word2"), sep = " ")
  
bigrams_filtered <- bigrams_separated %>% 
  filter(!word1 %in% stop_words$word) %>% 
  filter(!word2 %in% stop_words$word)
bigram_counts <- bigrams_filtered %>% 
  count(word1, word2, sort = TRUE)
bigram_counts
bigrams_united <- bigrams_filtered %>% 
  unite(bigram, word1, word2, sep = " ")
bigrams_united
austen_books() %>% 
  unnest_tokens(trigram, text, token = "ngrams", n = 3) %>% 
  separate(trigram, into = c("word1", "word2", "word3"), sep = " ") %>% 
  filter(!word1 %in% stop_words$word,
         !word2 %in% stop_words$word,
         !word3 %in% stop_words$word) %>% 
  count(word1, word2, word3, sort = TRUE)

Analyzing Bigrams

Identifying the “streets” in each book.

bigrams_filtered %>% 
  filter(word2 == "street") %>% 
  count(book, word1, sort = TRUE)
bigram_tf_idf <- bigrams_united %>% 
  count(book, bigram) %>% 
  bind_tf_idf(bigram, book, n) %>% 
  arrange(desc(tf_idf))
bigram_tf_idf 

The 12 bigrams with the highest tf-idf from each Jane Austen novel.

bigram_tf_idf %>% 
  arrange(desc(tf_idf)) %>% 
  mutate(bigram = factor(bigram, levels = rev(unique(bigram)))) %>% 
  group_by(book) %>% 
  top_n(12,tf_idf) %>% 
  ungroup() %>% 
  ggplot(aes(x = bigram, y = tf_idf, fill = book))+
    geom_col(show.legend = FALSE) +
    facet_wrap(~book, ncol = 2, scales = "free")+
    coord_flip()

Using Bigrams to Provide Context in Sentiment Analysis

bigrams_separated %>% 
  filter(word1 == "not") %>% 
  count(word1, word2, sort = TRUE)
AFINN <- get_sentiments("afinn") 
AFINN
not_words <- bigrams_separated %>% 
  rename(word = word2) %>% 
  filter(word1 == "not") %>% 
  inner_join(AFINN) %>% 
  count(word, score, sort = TRUE) %>% 
  ungroup()
Joining, by = "word"
    
    
not_words
NA

The 20 sords followed by “not” that had the greatest contribution to sentiment scores, in either a postivie or negative direction.

not_words %>% 
  mutate(contribution = n * score) %>% 
  arrange(desc(abs(contribution))) %>% 
  head(20) %>% 
  mutate(word = reorder(word, contribution)) %>% 
  ggplot(aes(word, n * score, fill = n * score > 0)) +
  geom_col(show.legend = FALSE) +
  xlab("Words preceded by \"not\"") +
  ylab("Sentiment score * number of occurences") +
  coord_flip()

negation_words <- c("not", "no", "never", "without")
negated_words <- bigrams_separated %>% 
  filter(word1 %in% negation_words) %>% 
  inner_join(AFINN, by = c(word2 = "word")) %>% 
  count(word1, word2, sort = TRUE) %>% 
  ungroup()
negated_words %>% 
  inner_join(not_words) %>% 
  mutate(contribution = n * score) %>% 
  arrange(desc(abs(contribution))) %>% 
  head(12) %>% 
  mutate(word = reorder(word, contribution)) %>% 
  ggplot(aes(word, n * score, fill = n * score > 0)) +
  geom_col(show.legend = FALSE) +
  xlab("Words preceded by \"not\"") +
  ylab("Sentiment score * number of occurences") +
  coord_flip() +
  facet_wrap(~word1)
Joining, by = "n"

Analyzing networks with igraph.

Create an igraph object from tidy data using the ‘graph_from_data_frame()’ function, which takes a data frame of edges with columns for “from”, “to” and edge attributes (in this case n).

library(igraph)

Attaching package: ‘igraph’

The following objects are masked from ‘package:dplyr’:

    as_data_frame, groups, union

The following objects are masked from ‘package:purrr’:

    compose, simplify

The following object is masked from ‘package:tidyr’:

    crossing

The following object is masked from ‘package:tibble’:

    as_data_frame

The following objects are masked from ‘package:stats’:

    decompose, spectrum

The following object is masked from ‘package:base’:

    union
bigram_counts
bigram_graph <- bigram_counts %>% 
  filter(n > 20) %>% 
  graph_from_data_frame()
bigram_graph
IGRAPH 48ae45c DN-- 91 77 -- 
+ attr: name (v/c), n (e/n)
+ edges from 48ae45c (vertex names):
 [1] sir     ->thomas     miss    ->crawford   captain ->wentworth 
 [4] miss    ->woodhouse  frank   ->churchill  lady    ->russell   
 [7] lady    ->bertram    sir     ->walter     miss    ->fairfax   
[10] colonel ->brandon    miss    ->bates      lady    ->catherine 
[13] sir     ->john       jane    ->fairfax    miss    ->tilney    
[16] lady    ->middleton  miss    ->bingley    thousand->pounds    
[19] miss    ->dashwood   miss    ->bennet     john    ->knightley 
[22] miss    ->morland    captain ->benwick    dear    ->miss      
+ ... omitted several edges
  

Commone bigrams in Pride and Prejudice, showing those that occurred more thatn 20 times and where neighter word was a stop word

library(ggraph)
set.seed(2017)
ggraph(bigram_graph, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1)

Common bigrams in Pride and Prejudice, with some polishing

set.seed(2016)
a <- grid::arrow(type = "closed", length = unit(.15, "inches"))
ggraph(bigram_graph, layout = "fr") +
  geom_edge_link(aes(edge_alpha = n), show.legend = FALSE,
                 arrow = a, end_cap = circle(.07, 'inches')) +
  geom_node_point(color = "lightblue", size = 5) +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1) +
  theme_void()

Topicmodels package

library(tm)
Loading required package: NLP

Attaching package: ‘NLP’

The following object is masked from ‘package:ggplot2’:

    annotate
data("AssociatedPress", package = "topicmodels")
AssociatedPress
<<DocumentTermMatrix (documents: 2246, terms: 10473)>>
Non-/sparse entries: 302031/23220327
Sparsity           : 99%
Maximal term length: 18
Weighting          : term frequency (tf)
terms <- Terms(AssociatedPress)
head(terms)
[1] "aaron"      "abandon"    "abandoned"  "abandoning" "abbott"     "abboud"    
# library(tidyverse)
# library(tidytext)
ap_td <- tidy(AssociatedPress)
ap_td
ap_sentiments <- ap_td %>% 
  inner_join(get_sentiments("bing"), by = c( term = "word"))
ap_sentiments
# ggplot2
ap_sentiments %>% 
  count(sentiment, term, wt = count) %>% 
  ungroup() %>% 
  filter(n>= 200) %>% 
  mutate(n = ifelse(sentiment == "negative", -n, n)) %>% 
  mutate(term = reorder(term, n)) %>% 
  ggplot(aes(term, n, fill = sentiment)) +
  geom_col() +
  ylab("Contribution to sentiment") +
  coord_flip()

library(methods)
data("data_corpus_inaugural", package = "quanteda")
inaug_dfm <- quanteda::dfm(data_corpus_inaugural, verbose = FALSE)
inaug_dfm
Document-feature matrix of: 58 documents, 9,357 features (91.8% sparse).
inaug_td <- tidy(inaug_dfm)
inaug_td
inaug_tf_idf <- inaug_td %>%
  bind_tf_idf(term, document, count) %>% 
  arrange(desc(tf_idf))
inaug_tf_idf
NA
inaug_tf_idf %>% 
  filter(document %in% c("1861-Lincoln", "1933-Roosevelt", "1961-Kennedy", "2009-Obama")) %>% 
  ggplot(aes(reorder(term,tf_idf), tf_idf, fill = tf_idf)) +
  geom_col()+
  coord_flip()+
  facet_wrap(~document)

library(tidyr)
year_term_counts <- inaug_td %>% 
  extract(document, "year", "(\\d+)", convert = TRUE) %>% 
  complete(year, term, fill = list(count = 0)) %>% 
  group_by(year) %>% 
  mutate(year_total = sum(count))
year_term_counts %>% 
  filter(term %in% c("god", "america", "foreign",
                     "union", "constitution", "freedom")) %>% 
  ggplot(aes(year, count / year_total)) +
  geom_point() +
  geom_smooth() +
  facet_wrap(~term, scales = "free_y") +
  scale_y_continuous(labels = scales::percent_format()) +
  ylab("% frequency of word in inaugural address")

Casting tidy text data into a matrix

ap_td %>% 
  cast_dtm(document, term, count)
<<DocumentTermMatrix (documents: 2246, terms: 10473)>>
Non-/sparse entries: 302031/23220327
Sparsity           : 99%
Maximal term length: 18
Weighting          : term frequency (tf)
library(Matrix)

Attaching package: ‘Matrix’

The following object is masked from ‘package:tidyr’:

    expand
m <- ap_td %>% 
  cast_sparse(document, term , count)
class(m)
[1] "dgCMatrix"
attr(,"package")
[1] "Matrix"
dim(m)
[1]  2246 10473
library(janeaustenr)
austen_dtm <- austen_books() %>% 
  unnest_tokens(word, text) %>% 
  count(book, word) %>% 
  cast_dtm(book, word, n)
austen_dtm
<<DocumentTermMatrix (documents: 6, terms: 14520)>>
Non-/sparse entries: 40379/46741
Sparsity           : 54%
Maximal term length: 19
Weighting          : term frequency (tf)

Tidying corpus objects with metadata

data("acq")
acq
<<VCorpus>>
Metadata:  corpus specific: 0, document level (indexed): 0
Content:  documents: 50
acq[[1]]
<<PlainTextDocument>>
Metadata:  15
Content:  chars: 1287
acq_td <- tidy(acq)
acq_td
acq_tokens <- acq_td %>% 
  select(-places) %>% 
  unnest_tokens(word, text) %>% 
  anti_join(stop_words, by = "word")
# most common words
acq_tokens %>% 
  count(word, sort = TRUE)
acq_tokens %>% 
  count(id, word) %>% 
  bind_tf_idf(word, id, n) %>% 
  arrange(desc(tf_idf))

Example: Mining Financial Articles

library(stringr)
stock_tf_idf <- stock_tokens %>% 
  count(company, word) %>%
  filter(!str_detect(word, "\\d+")) %>% 
  bind_tf_idf(word, company, n) %>% 
  arrange(-tf_idf)
  
stock_tf_idf
stock_tf_idf %>% 
  group_by(company) %>% 
  top_n(10, tf_idf) %>% 
  arrange(desc(tf_idf)) %>% 
  ungroup(company) %>% 
  mutate(word = factor(word, levels = rev(unique(word))),
         company = as.factor(company)) %>% 
  ggplot(aes(word, tf_idf, fill = company))+
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y = "tf-idf") +
  facet_wrap(~company, ncol = 2, scales = "free")+
  coord_flip()

Words with the largest contribution to sentiment scores in recent financial articles, according to the AFINN dictionary. The “contribution” is the product of the word and the sentiment score.

stock_tokens %>% 
  anti_join(stop_words, by = "word") %>% 
  count(word, id, sort = TRUE) %>% 
  inner_join(get_sentiments("afinn"), by = "word") %>% 
  group_by(word) %>% 
  summarize(contribution = sum(n * score)) %>% 
  top_n(12, abs(contribution)) %>% 
  mutate(word = reorder(word, contribution)) %>% 
  ggplot(aes(word, contribution)) +
  geom_col() +
  coord_flip() +
  labs(y = "Frequency of word * AFINN score")

stock_tokens %>% 
  count(word) %>% 
  inner_join(get_sentiments("loughran"), by = "word") %>% 
  group_by(sentiment) %>% 
  top_n(5, n) %>% 
  ungroup() %>%
  mutate(word = reorder(word, n)) %>% 
  ggplot(aes(word, n)) +
  geom_col() +
  coord_flip() +
  facet_wrap(~sentiment, scales = "free") +
  ylab("Frequency of this word in the recent financial articles")

NA
stock_sentiment_count <- stock_tokens %>% 
  inner_join(get_sentiments("loughran"), by = "word") %>% 
  count(sentiment, company) %>% 
  spread(sentiment, n, fill = 0)
stock_sentiment_count
stock_sentiment_count %>% 
  mutate(score = (positive - negative)/(positive + negative)) %>% 
  mutate(company = reorder(company, score)) %>% 
  ggplot(aes(company, score, fill = score > 0)) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  labs(x = "Company",
       y = "Positivity score amond 20 recent news articles")

Chapter 6 Toic Modeling

Topic modeling methods od unsupervised classification of documents, similar to clustering on numeric data, which finds natural groups of items even when we’re not sure what we’re looking for.

Latent Dirichlet allocation (LDA) popular methods for fitting a topic model. It treats each document as a mixture of topics, and each topic as a mixture of words.

library(topicmodels)
data("AssociatedPress")
AssociatedPress
<<DocumentTermMatrix (documents: 2246, terms: 10473)>>
Non-/sparse entries: 302031/23220327
Sparsity           : 99%
Maximal term length: 18
Weighting          : term frequency (tf)
ap_lda <- LDA(AssociatedPress, k = 2, control = list(seed = 1234))
ap_lda
A LDA_VEM topic model with 2 topics.
library(tidytext)
ap_topics <- tidy(ap_lda, matrix = "beta")
ap_topics

Terms that are most common within each topic.

# library(ggplot2)
# library(dplyr)
ap_top_terms <- ap_topics %>% 
  group_by(topic) %>%
  top_n(10, beta) %>% 
  ungroup() %>% 
  arrange(topic, -beta)
ap_top_terms %>% 
  mutate(term = reorder(term, beta)) %>% 
  ggplot(aes(term,beta, fill = factor(topic))) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~topic, scales = "free") +
  coord_flip()

# library(tidyr)
beta_spread <- ap_topics %>% 
  mutate(topic = paste0("topic", topic)) %>% 
  spread(topic, beta) %>% 
  filter(topic1 > .001 | topic2 > .001) %>% 
  mutate(log_ratio = log2(topic2/ topic1))
beta_spread %>% 
  filter(log_ratio >10 | log_ratio < -10) %>% 
  ggplot(aes(reorder(term,log_ratio),log_ratio)) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  labs(title = "Words with the greatest \n differences between two topics",
       x = "term",
       y = "Log2 ratio of beta in topic2/topic1")

Document-Topic Probabilities

ap_documents <- tidy(ap_lda, matrix = "gamma")
ap_documents
tidy(AssociatedPress) %>% 
  filter(document == 6) %>% 
  arrange(desc(count))

Example: The Great Library Heist

titles <- c("Twenty Thousand Leagues under the Sea", "The War of the Worlds",
            "Pride and Prejudice", "Great Expectations")
library(gutenbergr)
books <- gutenberg_works(title %in% titles) %>% 
  gutenberg_download(meta_fields = "title")
books
library(stringr)
# Divide into documents, each representing one chapter
reg <- regex("^chapter", ignore_case = TRUE)
by_chapter <- books %>% 
  group_by(title) %>% 
  mutate(chapter = cumsum(str_detect(text, reg))) %>% 
  ungroup() %>% 
  filter(chapter > 0) %>% 
  unite(document, title, chapter)
# Split into words
by_chapter_word <- by_chapter %>%
  unnest_tokens(word, text)
# Find document-word counts
word_counts <- by_chapter_word %>% 
  anti_join(stop_words) %>% 
  count(document, word, sort = TRUE) %>% 
  ungroup()
Joining, by = "word"
word_counts

LDA on chapters

chapters_dtm <- word_counts %>% 
  cast_dfm(document, word, n)
chapters_dtm
Document-feature matrix of: 194 documents, 18,215 features (97% sparse).
chapters_lda <- LDA(chapters_dtm, k = 4, control = list(seed = 1234))
chapters_lda
A LDA_VEM topic model with 4 topics.
chapter_topics <- tidy(chapters_lda, matrix = "beta")
chapter_topics
top_terms <- chapter_topics %>% 
  group_by(topic) %>% 
  top_n(5, beta) %>% 
  ungroup() %>% 
  arrange(topic, -beta)
top_terms
top_terms %>% 
  mutate(term = reorder(term, beta)) %>% 
  ggplot(aes(term, beta, fill - factor(topic))) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ topic, scales = "free") +
  coord_flip()

Per-Document Classification

chapters_gamma <- tidy(chapters_lda, matrix = "gamma")
chapters_gamma
chapters_gamma <- chapters_gamma %>% 
  separate(document, c("title", "chapter"), sep = "_", convert = TRUE)
chapters_gamma
# Reorder titles in orde of topic 1, topic 2, etc. before plotting
chapters_gamma %>% 
  mutate(title = reorder(title, gamma * topic)) %>% 
  ggplot(aes(factor(topic), gamma)) +
  geom_boxplot() +
  facet_wrap(~title)

chapter_classifications <- chapters_gamma %>% 
  group_by(title, chapter) %>% 
  top_n(1, gamma) %>% 
  ungroup()
chapter_classifications
book_topics <- chapter_classifications %>% 
  count(title, topic) %>% 
  group_by(title) %>% 
  top_n(1, n) %>% 
  ungroup() %>% 
  transmute(consensus = title, topic)
chapter_classifications %>% 
  inner_join(book_topics, by = "topic") %>% 
  filter(title != consensus)

By-Word Assignments:augment

# library(broom)
# assignments <- augment(chapters_lda, data = chapters_dtm)
# 
# assignments
#   
# assignments <- assignments %>%
#   separate(document, c("title", "chapter"), sep = "_", convert = TRUE) %>%
#   inner_join(book_topics, by = c(".topic" = "topic"))
# 
# assignments
# 
# assignments %>%
#   count(title, consensus, wt = count) %>%
#   group_by(title) %>%
#   mutate(percent = n / sum(n)) %>%
#   ggplot(aes(consensus, title, fill = percent)) +
#   geom_tile() +
#   scale_fill_gradient2(high = "red", label = percent_format()) +
#   theme_minimal() +
#   theme(axis.text.x = element_text(angle = 90, hjust = 1),
#         panel.grid = element_blank()) +
#   labs(x = "Book words were assigned to",
#        y = "Book words came from",
#        fill = "% of assignments")
# 
# 
# 
# wrong_words <- assignments %>%
#   filter(title != consensus)
# 
# wrong_words
# 
# wrong_words %>%
#   count(title, consensus, term, wt = count) %>%
#   ungroup() %>%
#   arrange(desc(n))
# 
# word_counts %>%
#   filter(word == "flopson")

Alternative LDA Implementations

library(mallet)

# create a vector with one string per chapter
collapsed <- by_chapter_word %>%
  anti_join(stop_words, by = "word") %>%
  mutate(word = str_replace(word, "'", "")) %>%
  group_by(document) %>%
  summarize(text = paste(word, collapse = " "))

# create an empty file of "stopwords"
file.create(empty_file <- tempfile())
docs <- mallet.import(collapsed$document, collapsed$text, empty_file)

mallet_model <- MalletLDA(num.topics = 4)
mallet_model$loadDocuments(docs)
mallet_model$train(100)
# word-topic pairs
tidy(mallet_model)

# document-topic pairs
tidy(mallet_model, matrix = "gamma")

# column needs to be named "term" for "augment"
term_counts <- rename(word_counts, term = word)
augment(mallet_model, term_counts)

Chapter 7

LS0tCnRpdGxlOiAiVGlkeSBUZXh0IE5vdGVib29rIgphdXRob3I6ICJTZWFuIE5ndXllbiIKc3VidGl0bGU6IEJ5IERhdmlkIFJvYmluc29uIGFuZCBKdWxpYSBTaWxnZQpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IGZsYXRseQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tCgpUaGlzIGlzIG15IG5vdGVib29rIHRoYXQgSSB3aWxsIHVzZWQgdG8gZ28gdGhyb3VnaCB0aGUgdGlkeSB0ZXh0IG5vdGVib29rIGJ5IEp1bGlhIFNpbGdlIGFuZCBEYXZpZCBSb2JpbnNvbi4gCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeXRleHQpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShqYW5lYXVzdGVucikKCgp0ZXh0IDwtIGMoIkJlY2F1c2UgSSBjb3VsZCBub3Qgc3RvcCBmb3IgRGVhdGggLSAiLCAKICAgICAgICAgICJIZSBraW5kbHkgc3RvcHBlZCBmb3IgbWUgLSIsCiAgICAgICAgICAiVGhlIENhcnJpYWdlIGhlbGQgYnV0IGp1c3QgT3Vyc2VsdmVzIC0iLAogICAgICAgICAgImFuZCBJbW1vcnRhbGl0eSIpCgp0ZXh0CmBgYAoKYGBge3J9CnRleHRfZGYgPC0gZGF0YV9mcmFtZShsaW5lID0gMTo0LCB0ZXh0ID0gdGV4dCkKCnRleHRfZGYKYGBgCmBgYHtyfQp0ZXh0X2RmICU+JSAKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICAjIHRvX2xvd2VyID0gRkFMU0UgdG8ga2VlcCB1cHBlcmNhc2UKYGBgCgojIyNXb3JraW5nIHdpdGggSmFuZSBBdXN0ZW4gQm9va3MKClVzaW5nIHRoZSBKYW5lIEF1c3RlbiBkYXRhc2V0IHdlIGNhbiBtYWtlIGl0IHRpZHkuICBGaXJzdCB3ZSdsbCB1c2UgbXV0YXRlIHRvIGNyZWF0ZSBhIGNvbHVtbiBmcm9tIGV4aXN0aW5nIGRhdGEuICBBbm5vdGF0ZSBsaW5lIG51bWJlcnMgYW5kIHRoZW4ga2VlcCB0cmFjayBvZiBjaGFwdGVycyB1c2luZyByZWdleC4KCmBgYHtyfQpvcmlnaW5hbF9ib29rcyA8LSBhdXN0ZW5fYm9va3MoKSAlPiUgCiAgZ3JvdXBfYnkoYm9vaykgJT4lIAogIG11dGF0ZShsaW5lbnVtYmVyID0gcm93X251bWJlcigpLAogICAgICAgICBjaGFwdGVyID0gY3Vtc3VtKHN0cl9kZXRlY3QodGV4dCwgcmVnZXgoIl5jaGFwdGVyIFtcXGRpdnhsY10iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlnbm9yZS5jYXNlID0gVFJVRSkpKSkgJT4lIAogIHVuZ3JvdXAoKQoKb3JpZ2luYWxfYm9va3MKYGBgCgpSZXN0cnVjdHVyZSBpdCBpbiAqb25lLXRva2VuLXBlci1yb3cqIGZvcm1hdCB3aXRoIHRoZSAqKnVubmVzdF90b2tlbnMoKSoqIGZ1bmN0aW9uLgoKYGBge3J9CnRpZHlfYm9va3MgPC0gb3JpZ2luYWxfYm9va3MgJT4lIAogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkKCnRpZHlfYm9va3MKYGBgCiAKIyMjI1JlbW92aW5nIHN0b3Agd29yZHMKV2UgY2FuIHJlbW92ZSAqc3RvcCB3b3JkcyogCgpgYGB7cn0KZGF0YShzdG9wX3dvcmRzKQoKdGlkeV9ib29rcyA8LSB0aWR5X2Jvb2tzICU+JSAKICBhbnRpX2pvaW4oc3RvcF93b3JkcykKCnRpZHlfYm9va3MgJT4lIAogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQoKCmBgYAoKUGxvdHRpbmcgZGF0YSAKCmBgYHtyfQp0aWR5X2Jvb2tzICU+JSAKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lIAogIGZpbHRlcihuID4gNjAwKSAlPiUgCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUgCiAgZ2dwbG90KGFlcyh3b3JkLCBuKSkrCiAgZ2VvbV9jb2woKSsKICB4bGFiKE5VTEwpKwogIGNvb3JkX2ZsaXAoKSsKICB0aGVtZV9idygpCmBgYAojIyMjIEd1dGVuYmVyZ3IKCkhHIHdlbGxzIGJvb2tzCmBgYHtyfQpsaWJyYXJ5KGd1dGVuYmVyZ3IpCgoKaGd3ZWxscyA8LSAgZ3V0ZW5iZXJnX2Rvd25sb2FkKGMoMzUsIDM2LCA1MjMwLCAxNTkpKQoKdGlkeV9oZ3dlbGxzIDwtIGhnd2VsbHMgJT4lIAogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lIAogIGFudGlfam9pbihzdG9wX3dvcmRzKQoKdGlkeV9oZ3dlbGxzICU+JSAKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkKYGBgCgpNb3N0IGNvbW1vbmUgd29yZHMgaW4gbm92ZWxzIGJ5IEJyb250ZSBzaXN0ZXJzCgpgYGB7cn0KYnJvbnRlIDwtICBndXRlbmJlcmdfZG93bmxvYWQoYygxMjYwLCA3NjgsIDk2OSwgOTE4MiwgNzY3KSkKCnRpZHlfYnJvbnRlIDwtIGJyb250ZSAlPiUgCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUgCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpCgp0aWR5X2Jyb250ZSAlPiUgCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpCgpgYGAKCkNvbXBhcmluZyBCcm9udGUgYW5kIEhHIFdlbGxzCgpgYGB7cn0KZnJlcXVlbmN5IDwtIGJpbmRfcm93cyhtdXRhdGUodGlkeV9icm9udGUsIGF1dGhvciA9ICJCcm9udGUgU2lzdGVycyIpLAogICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZSh0aWR5X2hnd2VsbHMsIGF1dGhvciA9ICJILkcuIFdlbGxzIiksCiAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKHRpZHlfYm9va3MsIGF1dGhvciA9ICJKYW5lIEF1c3RlbiIpKSAlPiUgCiAgbXV0YXRlKHdvcmQgPSBzdHJfZXh0cmFjdCh3b3JkLCAiW2EteiddKyIpKSAlPiUgCiAgY291bnQoYXV0aG9yLCB3b3JkKSAlPiUgCiAgZ3JvdXBfYnkoYXV0aG9yKSAlPiUgCiAgbXV0YXRlKHByb3BvcnRpb24gPSBuIC8gc3VtKG4pKSAlPiUgCiAgc2VsZWN0KC1uKSAlPiUgCiAgc3ByZWFkKGF1dGhvciwgcHJvcG9ydGlvbikgJT4lIAogIGdhdGhlcihhdXRob3IsIHByb3BvcnRpb24sIGBCcm9udGUgU2lzdGVyc2A6YEguRy4gV2VsbHNgKQpgYGAKCiMjI0dyYXBoaW5nIHRoZSBjb21wYXJpc29ucwoKYGBge3J9CmxpYnJhcnkoc2NhbGVzKQoKZ2dwbG90KGZyZXF1ZW5jeSwgYWVzKHggPSBwcm9wb3J0aW9uLCB5ID0gYEphbmUgQXVzdGVuYCwKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gYWJzKGBKYW5lIEF1c3RlbmAgLSBwcm9wb3J0aW9uKSkpICsKICBnZW9tX2FibGluZShjb2xvciA9ICJncmF5NDAiLCBsdHkgPSAyKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjEsIHNpemUgPSAyLjUsIHdpZHRoID0gMC4zLCBoZWlnaHQgPSAwLjMpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gd29yZCwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIHZqdXN0ID0gMS41KSkrCiAgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBwZXJjZW50X2Zvcm1hdCgpKSArCiAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBwZXJjZW50X2Zvcm1hdCgpKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobGltaXRzID0gYygwLDAuMDAxKSwKICAgICAgICAgICAgICAgICAgICAgICBsb3cgPSAiZGFya3NsYXRlZ3JheTQiLCBoaWdoID0gImdyYXk3NSIpICsKICBmYWNldF93cmFwKH5hdXRob3IsIG5jb2wgPSAyKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgbGFicyh5ID0gIkphbmUgQXVzdGVuIiwgeCA9IE5VTEwpCiAgCmBgYAoKIyMjQ29ycmVsYXRpb24gYW5hbHlzaXMKYGBge3J9CmNvci50ZXN0KGRhdGEgPSBmcmVxdWVuY3lbZnJlcXVlbmN5JGF1dGhvciA9PSAiQnJvbnRlIFNpc3RlcnMiLF0sCiAgICAgICAgIH4gcHJvcG9ydGlvbiArIGBKYW5lIEF1c3RlbmApCmBgYAoKYGBge3J9CmNvci50ZXN0KGRhdGEgPSBmcmVxdWVuY3lbZnJlcXVlbmN5JGF1dGhvciA9PSAiSC5HLiBXZWxscyIsXSwKICAgICAgICAgfiBwcm9wb3J0aW9uICsgYEphbmUgQXVzdGVuYCkKYGBgCgojIFNlbnRpbWVudHMgZGF0YXNldAoKYGBge3J9CmxpYnJhcnkodGlkeXRleHQpCgpzZW50aW1lbnRzCmBgYApTZW50aW1lbnQgc2NvcmVzCmBgYHtyfQojIEFGSU5OCmdldF9zZW50aW1lbnRzKCJhZmlubiIpCiMgQmluZwpnZXRfc2VudGltZW50cygiYmluZyIpCiMgTlJDCmdldF9zZW50aW1lbnRzKCJucmMiKQpgYGAKIFNlbnRpbWVudCBhbmFseXNpcyBjYW4gYmUgcGVyZm9ybWVkIHdpdGggYW4gKippbm5lciBqb2luKioKIAogCmBgYHtyfQp0aWR5X2Jvb2tzIDwtIGF1c3Rlbl9ib29rcygpICU+JSAKICBncm91cF9ieShib29rKSAlPiUgCiAgbXV0YXRlKGxpbmVudW1iZXIgPSByb3dfbnVtYmVyKCksCiAgICAgICAgIGNoYXB0ZXIgPSBjdW1zdW0oc3RyX2RldGVjdCh0ZXh0LCByZWdleCgiXmNoYXB0ZXIgW1xcZGl2eGxjXSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVfY2FzZSA9IFRSVUUpKSkpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkKCnRpZHlfYm9va3MKYGBgCgpHZXQgYSBsaXN0IG9mIHdvcmRzIHRoYXQgYXJlIGFzc29jaWF0ZWQgd2l0aCAnam95JyBhbmQgcGVyZm9ybSBhbiBpbm5lcl9qb2luIHRvIGZpbmQgd29yZHMgdGhhdCBhcmUgaGF2ZSAnam95JyBzZW50aW1lbnQgd2l0aGluIEphbmUgQXVzdGVuJ3MgRW1tYSBib29rLgpgYGB7cn0KCiMgRmlsdGVyIHdvcmRzIGFzc29jaWF0ZWQgd2l0aCAnam95JwpucmNqb3kgPC0gZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JSAKICBmaWx0ZXIoc2VudGltZW50ID09ICJqb3kiKQoKIyBGaW5kIHdvcmRzIGFzc29jaWF0ZWQgd2l0aCBqb3kgaW4gRW1tYQp0aWR5X2Jvb2tzICU+JSAKICBmaWx0ZXIoYm9vayA9PSAiRW1tYSIpICU+JSAKICBpbm5lcl9qb2luKG5yY2pveSkgJT4lIAogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQpgYGAKQ291bnQgdXAgcG9zaXRpdmUgb3IgbmVnYXRpdmUgd29yZHMgdGhhdCBhcmUgd2l0aGluIHNlY3Rpb25zIG9mIGVhY2ggYm9vay4gIFdlIHdpbGwgZGVmaW5lIGFuIGluZGV4IHRvIGtlZXAgdHJhY2sgb2Ygd2hlcmUgd2UgYXJlIGluIHRoZSBuYXJyYXRpdmUuICBUaGUgaW5kZXggd2lsbCBjb3VudCB1cCBzZWN0aW9ucyBvZiA4MCBsaW5lcyBvZiB0ZXh0CmBgYHtyfQpqYW5lYXVzdGVuc2VudGltZW50IDwtIHRpZHlfYm9va3MgJT4lIAogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lIAogIGNvdW50KGJvb2ssIGluZGV4ID0gbGluZW51bWJlciAlLyUgODAsIHNlbnRpbWVudCkgJT4lIAogIHNwcmVhZChzZW50aW1lbnQsIG4sIGZpbGwgPTApICU+JSAKICBtdXRhdGUoc2VudGltZW50ID0gcG9zaXRpdmUgLSBuZWdhdGl2ZSkKCmphbmVhdXN0ZW5zZW50aW1lbnQKYGBgCmBgYHtyfQpnZ3Bsb3QoamFuZWF1c3RlbnNlbnRpbWVudCwgYWVzKGluZGV4LCBzZW50aW1lbnQsIGZpbGwgPSBib29rKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH5ib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpCmBgYAoKYGBge3J9CnByaWRlX3ByZWp1ZGljZSA8LSB0aWR5X2Jvb2tzICU+JSAKICBmaWx0ZXIoYm9vayA9PSAiUHJpZGUgJiBQcmVqdWRpY2UiKQoKcHJpZGVfcHJlanVkaWNlCmBgYApgYGB7cn0KYWZpbm4gPC0gcHJpZGVfcHJlanVkaWNlICU+JSAKICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpKSAlPiUgCiAgZ3JvdXBfYnkoaW5kZXggPSBsaW5lbnVtYmVyICUvJSA4MCkgJT4lIAogIHN1bW1hcmlzZShzZW50aW1lbnQgPSBzdW0oc2NvcmUpKSAlPiUgCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpCgphZmlubgoKYGBgCmBgYHtyfQpiaW5nX2FuZF9ucmMgPC0gYmluZF9yb3dzKAogIHByaWRlX3ByZWp1ZGljZSAlPiUgCiAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JSAKICAgIG11dGF0ZShtZXRob2QgPSAiQmluZyBldCBhbC4iKSwKICBwcmlkZV9wcmVqdWRpY2UgJT4lIAogICAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygibnJjIikgJT4lIAogICAgICAgICAgICAgICAgIGZpbHRlcihzZW50aW1lbnQgJWluJSBjKCJwb3NpdGl2ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5lZ2F0aXZlIikpKSAlPiUgCiAgICBtdXRhdGUobWV0aG9kID0gIk5SQyIpKSAlPiUgCiAgICBjb3VudChtZXRob2QsIGluZGV4ID0gbGluZW51bWJlciAlLyUgODAsIHNlbnRpbWVudCkgJT4lIAogICAgc3ByZWFkKHNlbnRpbWVudCwgbiwgZmlsbCA9IDApICU+JSAKICAgIG11dGF0ZShzZW50aW1lbnQgPSBwb3NpdGl2ZSAtIG5lZ2F0aXZlKQoKYmluZ19hbmRfbnJjCmBgYApDb21wYXJpbmcgdGhyZWUgc2VudGltZW50IGxleGljb25zIHVzaW5nIFByaWRlIGFuZCBQcmVqdWRpY2UKYGBge3J9CmJpbmRfcm93cyhhZmlubiwKICAgICAgICAgIGJpbmdfYW5kX25yYykgJT4lIAogIGdncGxvdChhZXMoaW5kZXgsIHNlbnRpbWVudCwgZmlsbCA9IG1ldGhvZCkpICsKICAgIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgIGZhY2V0X3dyYXAofm1ldGhvZCwgbmNvbCA9IDEgLCBzY2FsZXMgPSAiZnJlZV95IikKYGBgCmBgYHtyfQpnZXRfc2VudGltZW50cygibnJjIikgJT4lIAogIGZpbHRlcihzZW50aW1lbnQgJWluJSBjKCJwb3NpdGl2ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIm5lZ2F0aXZlIikpICU+JSAKICBjb3VudChzZW50aW1lbnQpCgoKZ2V0X3NlbnRpbWVudHMoImJpbmciKSAlPiUgCiAgY291bnQoc2VudGltZW50KQpgYGAKYGBge3J9CmJpbmdfd29yZHNfY291bnRzIDwtIHRpZHlfYm9va3MgJT4lIAogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lIAogIGNvdW50KHdvcmQsIHNlbnRpbWVudCwgc29ydCA9IFRSVUUpICU+JSAKICB1bmdyb3VwKCkKCmJpbmdfd29yZHNfY291bnRzCmBgYApgYGB7cn0KYmluZ193b3Jkc19jb3VudHMgJT4lIAogIGdyb3VwX2J5KHNlbnRpbWVudCkgJT4lIAogIHRvcF9uKDEwKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JSAKICBnZ3Bsb3QoYWVzKHdvcmQsIG4sIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkrCiAgZmFjZXRfd3JhcCh+c2VudGltZW50LCBzY2FsZXMgPSAiZnJlZV95IikgKwogIGxhYnMoeSA9ICJDb250cmlidXRpb24gdG8gc2VudGltZW50IiwKICAgICAgIHggPSBOVUxMLAogICAgICAgdGl0bGUgPSAiVG9wIDEwIHdvcmRzIGJ5IFNlbnRpbWVudCBmb3IgUHJpZGUgYW5kIFByZWp1ZGljZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCk1pc3MgaXMgcmVmZXJyZWQgdG8gYXMgYSBuZWdhdGl2ZSB3b3JkIGJ1dCBpcyBub3QgaW4gSmFuZSBBdXN0ZW4ncyB3b3Jrcy4KYGBge3J9CmN1c3RvbV9zdG9wX3dvcmRzIDwtIGJpbmRfcm93cyhkYXRhX2ZyYW1lKHdvcmQgPSBjKCJtaXNzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxleGljb24gPSBjKCJjdXN0b20iKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wX3dvcmRzKQoKY3VzdG9tX3N0b3Bfd29yZHMKYGBgCiMgV29yZGNsb3VkcwoKYGBge3J9CmxpYnJhcnkod29yZGNsb3VkKQoKIyBodHRwczovL3JzdHVkaW8tcHVicy1zdGF0aWMuczMuYW1hem9uYXdzLmNvbS8xMDQzNjZfNmI3ODc4NzQ2YzQ0NDQ2MjhlYWVkODg5NGQ2MTdiYmEuaHRtbApnZ0NvbG9ycyA8LSBmdW5jdGlvbihuKSB7CiAgaHVlcyA9IHNlcSgxNSwgMzc1LCBsZW5ndGg9bisxKQogIGhjbChoPWh1ZXMsIGw9NjUsIGM9MTAwKVsxOm5dCn0KZ2cuY29scyA8LSBnZ0NvbG9ycyg4KQoKdGlkeV9ib29rcyAlPiUgCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JSAKICBjb3VudCh3b3JkKSAlPiUgCiAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gMTAwLCByYW5kb20uY29sb3I9VFJVRSwgCiAgICAgICAgICBjb2xvcnM9Z2cuY29scykpCgpgYGAKCmBgYHtyfQpsaWJyYXJ5KHJlc2hhcGUyKQoKdGlkeV9ib29rcyAlPiUgCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUgCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lIAogIGFjYXN0KHdvcmR+c2VudGltZW50LCB2YWx1ZS5jYXIgPSAibiIsIGZpbGwgPSAwKSAlPiUgCiAgY29tcGFyaXNvbi5jbG91ZChjb2xvcnMgPSBjKCJncmF5MjAiLCAiZ3JheTgwIiksCiAgICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSAxMDApCmBgYAoKTG9va2luZyBhdCBVbml0cyBCZXlvbmQgSnVzdCBXb3JkcwpgYGB7cn0KUGFuZFBfc2VudGVuY2VzIDwtICBkYXRhX2ZyYW1lKHRleHQgPSBwcmlkZXByZWp1ZGljZSkgJT4lIAogIHVubmVzdF90b2tlbnMoc2VudGVuY2UsIHRleHQsIHRva2VuID0gInNlbnRlbmNlcyIpCmBgYAoKYGBge3J9ClBhbmRQX3NlbnRlbmNlcyRzZW50ZW5jZVsyXQpgYGAKRmlndXJpbmcgb3V0IGhvdyBtYW55IGNoYXB0ZXJzIGFyZSBpbiBhIGJvb2sKYGBge3J9CmF1c3Rlbl9jaGFwdGVycyA8LSBhdXN0ZW5fYm9va3MoKSAlPiUgCiAgZ3JvdXBfYnkoYm9vaykgJT4lIAogIHVubmVzdF90b2tlbnMoY2hhcHRlciwgdGV4dCAsIHRva2VuID0gInJlZ2V4IiwKICAgICAgICAgICAgICAgIHBhdHRlcm4gPSAiQ2hhcHRlcnxDSEFQVEVSIFtcXGRJVlhMQ10iKSAlPiUgCiAgdW5ncm91cCgpCgoKYXVzdGVuX2NoYXB0ZXJzICU+JSAKICBncm91cF9ieShib29rKSAlPiUgCiAgc3VtbWFyaXNlKGNoYXB0ZXJzID0gbigpKQpgYGAKCgpgYGB7cn0KYmluZ25lZ2F0aXZlIDwtIGdldF9zZW50aW1lbnRzKCJiaW5nIikgJT4lIAogIGZpbHRlcihzZW50aW1lbnQgPT0gIm5lZ2F0aXZlIikKCndvcmRjb3VudHMgPC0gdGlkeV9ib29rcyAlPiUgCiAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lIAogIHN1bW1hcml6ZSh3b3JkcyA9IG4oKSkKCnRpZHlfYm9va3MgJT4lIAogIHNlbWlfam9pbihiaW5nbmVnYXRpdmUpICU+JSAKICBncm91cF9ieShib29rLCBjaGFwdGVyKSAlPiUgCiAgc3VtbWFyaXplKG5lZ2F0aXZld29yZHMgPSBuKCkpICU+JSAKICBsZWZ0X2pvaW4od29yZGNvdW50cywgYnkgPSBjKCJib29rIiwgImNoYXB0ZXIiKSkgJT4lIAogIG11dGF0ZShyYXRpbyA9IG5lZ2F0aXZld29yZHMvd29yZHMpICU+JSAKICBmaWx0ZXIoY2hhcHRlciAhPTApICU+JSAKICB0b3BfbigxKSAlPiUgCiAgdW5ncm91cCgpCmBgYAogCiAjIENoYXB0ZXIgMwogCiBUZXJtIEZyZXF1ZW5jeSBpbiBKYW5lIEF1c3RlbidzIE5vdmVscwogCmBgYHtyfQpib29rX3dvcmRzIDwtIGF1c3Rlbl9ib29rcygpICU+JSAKICB1bm5lc3RfdG9rZW5zKHdvcmQsdGV4dCkgJT4lIAogIGNvdW50KGJvb2ssd29yZCxzb3J0ID0gVFJVRSkgJT4lIAogIHVuZ3JvdXAoKQoKdG90YWxfd29yZHMgPC0gYm9va193b3JkcyAlPiUgCiAgZ3JvdXBfYnkoYm9vaykgJT4lIAogIHN1bW1hcml6ZSh0b3RhbCA9IHN1bShuKSkKCmJvb2tfd29yZHMgPC0gbGVmdF9qb2luKGJvb2tfd29yZHMsIHRvdGFsX3dvcmRzKQoKYm9va193b3JkcwpgYGAKClRlcm0gZnJlcXVlbmN5IGRpc3RyaWJ1dGlvbiBpbiBKYW5lIEF1c3RlbidzIG5vdmVscwpgYGB7cn0KZ2dwbG90KGJvb2tfd29yZHMsIGFlcyhuL3RvdGFsLCBmaWxsID0gYm9vaykpICsKICBnZW9tX2hpc3RvZ3JhbShzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgeGxpbShOQSwgMC4wMDA5KSArCiAgZmFjZXRfd3JhcCh+Ym9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3kiKQogIApgYGAKClppcGYncyBMYXcKClppcGYncyBMYXc6IGZyZXF1ZW5jeSB0aGF0IGEgd29yZCBhcHBlYXJzIGlzIGludmVyc2VseSBwcm9wb3J0aW9uYWwgdG8gaXRzIHJhbmsuCgpgYGB7cn0KZnJlcV9ieV9yYW5rIDwtIGJvb2tfd29yZHMgJT4lIAogIGdyb3VwX2J5KGJvb2spICU+JSAKICBtdXRhdGUocmFuayA9IHJvd19udW1iZXIoKSwKICAgICAgICAgJ3Rlcm0gZnJlcXVlbmN5JyA9IG4vdG90YWwpCgpmcmVxX2J5X3JhbmsKYGBgCgpHcmFwaCBvZiBaaXBmJ3MgbGF3IGZvciBKYW5lIEF1c3RlbidzIG5vdmVscwpgYGB7cn0KCmZyZXFfYnlfcmFuayAlPiUgCiAgZ2dwbG90KGFlcyhyYW5rLCBgdGVybSBmcmVxdWVuY3lgLCBjb2xvciA9IGJvb2spKSArCiAgZ2VvbV9saW5lKHNpemUgPSAxLjEsIGFscGhhID0gMC44LCBzaG93LmxlZ2VuZCA9IFRSVUUpICsKICBzY2FsZV94X2xvZzEwKCkgKwogIHNjYWxlX3lfbG9nMTAoKQpgYGAKCmBgYHtyfQpyYW5rX3N1YnNldCA8LSAgZnJlcV9ieV9yYW5rICU+JSAKICBmaWx0ZXIocmFuayA8NTAwLAogICAgICAgICByYW5rID4xMCkKCgpsbShsb2cxMChgdGVybSBmcmVxdWVuY3lgKSB+IGxvZzEwKHJhbmspLCBkYXRhID0gcmFua19zdWJzZXQpCmBgYApgYGB7cn0KZnJlcV9ieV9yYW5rICU+JSAKICBnZ3Bsb3QoYWVzKHJhbmssIGB0ZXJtIGZyZXF1ZW5jeWAsIGNvbG9yID0gYm9vaykpKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IC0wLjYyLCBzbG9wZSA9IC0xLjEsIGNvbG9yID0gImdyYXk1MCIsIGxpbmV0eXBlID0gMikgKwogIGdlb21fbGluZShzaXplID0gMS4xLCBhbHBoYSA9IDAuOCwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX3hfbG9nMTAoKSArCiAgc2NhbGVfeV9sb2cxMCgpCgpgYGAKClRoZSBiaW5kX3RmX2lkZiBGdW5jdGlvbgoKYGBge3J9CmJvb2tfd29yZHMgPC0gYm9va193b3JkcyAlPiUgCiAgYmluZF90Zl9pZGYod29yZCwgYm9vaywgbikKCmJvb2tfd29yZHMKYGBgCgpgYGB7cn0KYm9va193b3JkcyAlPiUgCiAgc2VsZWN0KC10b3RhbCkgJT4lIAogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQpgYGAKCmBgYHtyfQpib29rX3dvcmRzICU+JSAKICBhcnJhbmdlKGRlc2ModGZfaWRmKSkgJT4lIAogIG11dGF0ZSh3b3JkID0gZmFjdG9yKHdvcmQsIGxldmVscyA9IHJldih1bmlxdWUod29yZCkpKSkgJT4lIAogIGdyb3VwX2J5KGJvb2spICU+JSAKICB0b3BfbigxNSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZ2dwbG90KGFlcyh3b3JkLCB0Zl9pZGYsIGZpbGwgPSBib29rKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLCB5ID0gInRmLWlkZiIpICsKICBmYWNldF93cmFwKH5ib29rLCBuY29sID0gMixzY2FsZXMgPSAiZnJlZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCkEgY29ycHVzIG9mIFBoeXNpY3MgVGV4dAoKYGBge3J9CnBoeXNpY3MgPC0gZ3V0ZW5iZXJnX2Rvd25sb2FkKGMoMzc3MjksIDE0NzI1LCAxMzQ3NiwgNTAwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGFfZmllbGRzID0gImF1dGhvciIpCmBgYAoKYGBge3J9CnBoeXNpY3Nfd29yZHMgPC0gcGh5c2ljcyAlPiUgCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUgCiAgY291bnQoYXV0aG9yLCB3b3JkLCBzb3J0ID0gVFJVRSkgJT4lIAogIHVuZ3JvdXAoKQoKcGh5c2ljc193b3JkcwpgYGAKCmBgYHtyfQphdXRob3Jfb3JkZXIgPC0gYygiR2FsaWxlaSwgR2FsaWxlbyIsICJIdXlnZW5zLCBDaHJpc3RpYWFuIiwgIlRlc2xhLCBOaWtvbGEiLCAiRWluc3RlaW4sIEFsYmVydCIpCgpwbG90X3BoeXNpY3MgPC0gcGh5c2ljc193b3JkcyAlPiUgCiAgYmluZF90Zl9pZGYod29yZCwgYXV0aG9yLCBuKSAlPiUgCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JSAKICBtdXRhdGUod29yZCA9IGZhY3Rvcih3b3JkLCBsZXZlbHMgPSByZXYodW5pcXVlKHdvcmQpKSkpICU+JSAKICBtdXRhdGUoYXV0aG9yID0gZmFjdG9yKGF1dGhvciwgbGV2ZWxzID0gYXV0aG9yX29yZGVyKSkKCnBsb3RfcGh5c2ljcwpgYGAKCmBgYHtyfQpwbG90X3BoeXNpY3MgJT4lIAogIGdyb3VwX2J5KGF1dGhvcikgJT4lCiAgdG9wX24oMTUsIHRmX2lkZikgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIHRmX2lkZikpICU+JSAKICBnZ3Bsb3QoYWVzKHdvcmQsIHRmX2lkZiwgZmlsbCA9IGF1dGhvcikpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwgeSA9ICJ0Zi1pZGYiKSArCiAgZmFjZXRfd3JhcCh+YXV0aG9yLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCiAgCgpgYGAKCklzb2xhdGluZyAiZXEiIGZyb20gRWluc3RlaW4ncyB0ZXh0CgpgYGB7cn0KcGh5c2ljcyAlPiUgCiAgZmlsdGVyKHN0cl9kZXRlY3QodGV4dCwgImVxXFwuIikpICU+JSAKICBzZWxlY3QodGV4dCkKYGBgCksxIHdhcyB1c2VkIGZvciB0aGUgY29vcmRpbmF0ZSBzeXN0ZW0gZm9yIEVpbnN0ZWluCmBgYHtyfQoKcGh5c2ljcyAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdCh0ZXh0LCAiSzEiKSkgJT4lIAogIHNlbGVjdCh0ZXh0KQoKICAKYGBgCmBgYHtyfQpwaHlzaWNzICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KHRleHQsICJBSyIpKSAlPiUgCiAgc2VsZWN0KHRleHQpCmBgYAoKYGBge3J9Cm15c3RvcHdvcmRzIDwtIGRhdGFfZnJhbWUod29yZCA9IGMoImVxIiwiY28iLCJyYyIsImFjIiwiYWsiLCJibiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImZpZyIsICJmaWxlIiwgImNnIiwgImNiIiwgImNtIikpCgpwaHlzaWNzX3dvcmRzIDwtIGFudGlfam9pbihwaHlzaWNzX3dvcmRzLCBteXN0b3B3b3JkcywgYnkgPSAid29yZCIpCgpwbG90X3BoeXNpY3MgPC0gcGh5c2ljc193b3JkcyAlPiUgCiAgYmluZF90Zl9pZGYod29yZCwgYXV0aG9yLCBuKSAlPiUgCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JSAKICBtdXRhdGUod29yZCA9IGZhY3Rvcih3b3JkLCBsZXZlbHMgPSByZXYodW5pcXVlKHdvcmQpKSkpICU+JSAKICBncm91cF9ieShhdXRob3IpICU+JSAKICB0b3BfbigxNSwgdGZfaWRmKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGUoYXV0aG9yID0gZmFjdG9yKGF1dGhvciwgbGV2ZWxzID0gYXV0aG9yX29yZGVyKSkKCnBsb3RfcGh5c2ljcwpgYGAKCmBgYHtyfQpnZ3Bsb3QocGxvdF9waHlzaWNzLCBhZXMod29yZCwgdGZfaWRmLCBmaWxsID0gYXV0aG9yKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKCB4ID0gTlVMTCwgeSA9ICJ0Zi1pZGYiKSArCiAgZmFjZXRfd3JhcCh+YXV0aG9yLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgpgYGAKCiMgQ2hhcHRlciA0IFJlbGF0aW9uc2hpcHMgQmV0d2VlbiBXb3JkczogTi1ncmFtcyBhbmQgQ29ycmVsYXRpb25zCgpgYGB7cn0KYXVzdGVuX2JpZ3JhbXMgPC0gYXVzdGVuX2Jvb2tzKCkgJT4lIAogIHVubmVzdF90b2tlbnMoYmlncmFtLCB0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikKCmF1c3Rlbl9iaWdyYW1zCgpgYGAKQ291bnRpbmcgYW5kIEZpbHRlcmluZyBOLWdyYW1zCmBgYHtyfQphdXN0ZW5fYmlncmFtcyAlPiUgCiAgY291bnQoYmlncmFtLCBzb3J0ID0gVFJVRSkKYGBgCgpgYGB7cn0KYmlncmFtc19zZXBhcmF0ZWQgPC0gIGF1c3Rlbl9iaWdyYW1zICU+JSAKICBzZXBhcmF0ZShiaWdyYW0sIGludG8gPSBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpCiAgCmJpZ3JhbXNfZmlsdGVyZWQgPC0gYmlncmFtc19zZXBhcmF0ZWQgJT4lIAogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JSAKICBmaWx0ZXIoIXdvcmQyICVpbiUgc3RvcF93b3JkcyR3b3JkKQoKCmJpZ3JhbV9jb3VudHMgPC0gYmlncmFtc19maWx0ZXJlZCAlPiUgCiAgY291bnQod29yZDEsIHdvcmQyLCBzb3J0ID0gVFJVRSkKCmJpZ3JhbV9jb3VudHMKYGBgCgpgYGB7cn0KYmlncmFtc191bml0ZWQgPC0gYmlncmFtc19maWx0ZXJlZCAlPiUgCiAgdW5pdGUoYmlncmFtLCB3b3JkMSwgd29yZDIsIHNlcCA9ICIgIikKCmJpZ3JhbXNfdW5pdGVkCmBgYAoKYGBge3J9CgphdXN0ZW5fYm9va3MoKSAlPiUgCiAgdW5uZXN0X3Rva2Vucyh0cmlncmFtLCB0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMykgJT4lIAogIHNlcGFyYXRlKHRyaWdyYW0sIGludG8gPSBjKCJ3b3JkMSIsICJ3b3JkMiIsICJ3b3JkMyIpLCBzZXAgPSAiICIpICU+JSAKICBmaWx0ZXIoIXdvcmQxICVpbiUgc3RvcF93b3JkcyR3b3JkLAogICAgICAgICAhd29yZDIgJWluJSBzdG9wX3dvcmRzJHdvcmQsCiAgICAgICAgICF3b3JkMyAlaW4lIHN0b3Bfd29yZHMkd29yZCkgJT4lIAogIGNvdW50KHdvcmQxLCB3b3JkMiwgd29yZDMsIHNvcnQgPSBUUlVFKQpgYGAKCiMjI0FuYWx5emluZyBCaWdyYW1zCgpJZGVudGlmeWluZyB0aGUgInN0cmVldHMiIGluIGVhY2ggYm9vay4KCmBgYHtyfQpiaWdyYW1zX2ZpbHRlcmVkICU+JSAKICBmaWx0ZXIod29yZDIgPT0gInN0cmVldCIpICU+JSAKICBjb3VudChib29rLCB3b3JkMSwgc29ydCA9IFRSVUUpCmBgYAoKYGBge3J9CmJpZ3JhbV90Zl9pZGYgPC0gYmlncmFtc191bml0ZWQgJT4lIAogIGNvdW50KGJvb2ssIGJpZ3JhbSkgJT4lIAogIGJpbmRfdGZfaWRmKGJpZ3JhbSwgYm9vaywgbikgJT4lIAogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQoKYmlncmFtX3RmX2lkZiAKYGBgCgpUaGUgMTIgYmlncmFtcyB3aXRoIHRoZSBoaWdoZXN0IHRmLWlkZiBmcm9tIGVhY2ggSmFuZSBBdXN0ZW4gbm92ZWwuCmBgYHtyfQpiaWdyYW1fdGZfaWRmICU+JSAKICBhcnJhbmdlKGRlc2ModGZfaWRmKSkgJT4lIAogIG11dGF0ZShiaWdyYW0gPSBmYWN0b3IoYmlncmFtLCBsZXZlbHMgPSByZXYodW5pcXVlKGJpZ3JhbSkpKSkgJT4lIAogIGdyb3VwX2J5KGJvb2spICU+JSAKICB0b3BfbigxMix0Zl9pZGYpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGdncGxvdChhZXMoeCA9IGJpZ3JhbSwgeSA9IHRmX2lkZiwgZmlsbCA9IGJvb2spKSsKICAgIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgIGZhY2V0X3dyYXAofmJvb2ssIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZSIpKwogICAgY29vcmRfZmxpcCgpCgpgYGAKVXNpbmcgQmlncmFtcyB0byBQcm92aWRlIENvbnRleHQgaW4gU2VudGltZW50IEFuYWx5c2lzCmBgYHtyfQpiaWdyYW1zX3NlcGFyYXRlZCAlPiUgCiAgZmlsdGVyKHdvcmQxID09ICJub3QiKSAlPiUgCiAgY291bnQod29yZDEsIHdvcmQyLCBzb3J0ID0gVFJVRSkKYGBgCgpgYGB7cn0KQUZJTk4gPC0gZ2V0X3NlbnRpbWVudHMoImFmaW5uIikgCgpBRklOTgpgYGAKCmBgYHtyfQpub3Rfd29yZHMgPC0gYmlncmFtc19zZXBhcmF0ZWQgJT4lIAogIHJlbmFtZSh3b3JkID0gd29yZDIpICU+JSAKICBmaWx0ZXIod29yZDEgPT0gIm5vdCIpICU+JSAKICBpbm5lcl9qb2luKEFGSU5OKSAlPiUgCiAgY291bnQod29yZCwgc2NvcmUsIHNvcnQgPSBUUlVFKSAlPiUgCiAgdW5ncm91cCgpCiAgICAKICAgIApub3Rfd29yZHMKICAKYGBgCgoKClRoZSAyMCBzb3JkcyBmb2xsb3dlZCBieSAibm90IiB0aGF0IGhhZCB0aGUgZ3JlYXRlc3QgY29udHJpYnV0aW9uIHRvIHNlbnRpbWVudCBzY29yZXMsIGluIGVpdGhlciBhIHBvc3RpdmllIG9yIG5lZ2F0aXZlIGRpcmVjdGlvbi4KYGBge3J9Cm5vdF93b3JkcyAlPiUgCiAgbXV0YXRlKGNvbnRyaWJ1dGlvbiA9IG4gKiBzY29yZSkgJT4lIAogIGFycmFuZ2UoZGVzYyhhYnMoY29udHJpYnV0aW9uKSkpICU+JSAKICBoZWFkKDIwKSAlPiUgCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIGNvbnRyaWJ1dGlvbikpICU+JSAKICBnZ3Bsb3QoYWVzKHdvcmQsIG4gKiBzY29yZSwgZmlsbCA9IG4gKiBzY29yZSA+IDApKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHhsYWIoIldvcmRzIHByZWNlZGVkIGJ5IFwibm90XCIiKSArCiAgeWxhYigiU2VudGltZW50IHNjb3JlICogbnVtYmVyIG9mIG9jY3VyZW5jZXMiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKYGBge3J9Cm5lZ2F0aW9uX3dvcmRzIDwtIGMoIm5vdCIsICJubyIsICJuZXZlciIsICJ3aXRob3V0IikKCm5lZ2F0ZWRfd29yZHMgPC0gYmlncmFtc19zZXBhcmF0ZWQgJT4lIAogIGZpbHRlcih3b3JkMSAlaW4lIG5lZ2F0aW9uX3dvcmRzKSAlPiUgCiAgaW5uZXJfam9pbihBRklOTiwgYnkgPSBjKHdvcmQyID0gIndvcmQiKSkgJT4lIAogIGNvdW50KHdvcmQxLCB3b3JkMiwgc29ydCA9IFRSVUUpICU+JSAKICB1bmdyb3VwKCkKCgoKbmVnYXRlZF93b3JkcyAlPiUgCiAgaW5uZXJfam9pbihub3Rfd29yZHMpICU+JSAKICBtdXRhdGUoY29udHJpYnV0aW9uID0gbiAqIHNjb3JlKSAlPiUgCiAgYXJyYW5nZShkZXNjKGFicyhjb250cmlidXRpb24pKSkgJT4lIAogIGhlYWQoMTIpICU+JSAKICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgY29udHJpYnV0aW9uKSkgJT4lIAogIGdncGxvdChhZXMod29yZCwgbiAqIHNjb3JlLCBmaWxsID0gbiAqIHNjb3JlID4gMCkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgeGxhYigiV29yZHMgcHJlY2VkZWQgYnkgXCJub3RcIiIpICsKICB5bGFiKCJTZW50aW1lbnQgc2NvcmUgKiBudW1iZXIgb2Ygb2NjdXJlbmNlcyIpICsKICBjb29yZF9mbGlwKCkgKwogIGZhY2V0X3dyYXAofndvcmQxKQpgYGAKCgpBbmFseXppbmcgbmV0d29ya3Mgd2l0aCBpZ3JhcGguCgpDcmVhdGUgYW4gaWdyYXBoIG9iamVjdCBmcm9tIHRpZHkgZGF0YSB1c2luZyB0aGUgJ2dyYXBoX2Zyb21fZGF0YV9mcmFtZSgpJyBmdW5jdGlvbiwgd2hpY2ggdGFrZXMgYSBkYXRhIGZyYW1lIG9mIGVkZ2VzIHdpdGggY29sdW1ucyBmb3IgImZyb20iLCAidG8iIGFuZCBlZGdlIGF0dHJpYnV0ZXMgKGluIHRoaXMgY2FzZSBuKS4KCmBgYHtyfQpsaWJyYXJ5KGlncmFwaCkKCmJpZ3JhbV9jb3VudHMKCgpiaWdyYW1fZ3JhcGggPC0gYmlncmFtX2NvdW50cyAlPiUgCiAgZmlsdGVyKG4gPiAyMCkgJT4lIAogIGdyYXBoX2Zyb21fZGF0YV9mcmFtZSgpCgpiaWdyYW1fZ3JhcGgKICAKYGBgCgpDb21tb25lIGJpZ3JhbXMgaW4gUHJpZGUgYW5kIFByZWp1ZGljZSwgc2hvd2luZyB0aG9zZSB0aGF0IG9jY3VycmVkIG1vcmUgdGhhdG4gMjAgdGltZXMgYW5kIHdoZXJlIG5laWdodGVyIHdvcmQgd2FzIGEgc3RvcCB3b3JkCmBgYHtyfQpsaWJyYXJ5KGdncmFwaCkKCnNldC5zZWVkKDIwMTcpCgpnZ3JhcGgoYmlncmFtX2dyYXBoLCBsYXlvdXQgPSAiZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoKSArCiAgZ2VvbV9ub2RlX3BvaW50KCkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCB2anVzdCA9IDEsIGhqdXN0ID0gMSkKCgpgYGAKQ29tbW9uIGJpZ3JhbXMgaW4gUHJpZGUgYW5kIFByZWp1ZGljZSwgd2l0aCBzb21lIHBvbGlzaGluZwpgYGB7cn0Kc2V0LnNlZWQoMjAxNikKCgphIDwtIGdyaWQ6OmFycm93KHR5cGUgPSAiY2xvc2VkIiwgbGVuZ3RoID0gdW5pdCguMTUsICJpbmNoZXMiKSkKCmdncmFwaChiaWdyYW1fZ3JhcGgsIGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluayhhZXMoZWRnZV9hbHBoYSA9IG4pLCBzaG93LmxlZ2VuZCA9IEZBTFNFLAogICAgICAgICAgICAgICAgIGFycm93ID0gYSwgZW5kX2NhcCA9IGNpcmNsZSguMDcsICdpbmNoZXMnKSkgKwogIGdlb21fbm9kZV9wb2ludChjb2xvciA9ICJsaWdodGJsdWUiLCBzaXplID0gNSkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCB2anVzdCA9IDEsIGhqdXN0ID0gMSkgKwogIHRoZW1lX3ZvaWQoKQoKCgpgYGAKCiNUb3BpY21vZGVscyBwYWNrYWdlCmBgYHtyfQpsaWJyYXJ5KHRtKQoKZGF0YSgiQXNzb2NpYXRlZFByZXNzIiwgcGFja2FnZSA9ICJ0b3BpY21vZGVscyIpCgpBc3NvY2lhdGVkUHJlc3MKYGBgCgpgYGB7cn0KdGVybXMgPC0gVGVybXMoQXNzb2NpYXRlZFByZXNzKQpoZWFkKHRlcm1zKQpgYGAKCmBgYHtyfQojIGxpYnJhcnkodGlkeXZlcnNlKQojIGxpYnJhcnkodGlkeXRleHQpCgphcF90ZCA8LSB0aWR5KEFzc29jaWF0ZWRQcmVzcykKYXBfdGQKYGBgCgpgYGB7cn0KYXBfc2VudGltZW50cyA8LSBhcF90ZCAlPiUgCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpLCBieSA9IGMoIHRlcm0gPSAid29yZCIpKQoKCmFwX3NlbnRpbWVudHMKYGBgCgpgYGB7cn0KIyBnZ3Bsb3QyCgphcF9zZW50aW1lbnRzICU+JSAKICBjb3VudChzZW50aW1lbnQsIHRlcm0sIHd0ID0gY291bnQpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGZpbHRlcihuPj0gMjAwKSAlPiUgCiAgbXV0YXRlKG4gPSBpZmVsc2Uoc2VudGltZW50ID09ICJuZWdhdGl2ZSIsIC1uLCBuKSkgJT4lIAogIG11dGF0ZSh0ZXJtID0gcmVvcmRlcih0ZXJtLCBuKSkgJT4lIAogIGdncGxvdChhZXModGVybSwgbiwgZmlsbCA9IHNlbnRpbWVudCkpICsKICBnZW9tX2NvbCgpICsKICB5bGFiKCJDb250cmlidXRpb24gdG8gc2VudGltZW50IikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KG1ldGhvZHMpCgpkYXRhKCJkYXRhX2NvcnB1c19pbmF1Z3VyYWwiLCBwYWNrYWdlID0gInF1YW50ZWRhIikKaW5hdWdfZGZtIDwtIHF1YW50ZWRhOjpkZm0oZGF0YV9jb3JwdXNfaW5hdWd1cmFsLCB2ZXJib3NlID0gRkFMU0UpCgppbmF1Z19kZm0KYGBgCgpgYGB7cn0KaW5hdWdfdGQgPC0gdGlkeShpbmF1Z19kZm0pCmluYXVnX3RkCmBgYAoKYGBge3J9CgppbmF1Z190Zl9pZGYgPC0gaW5hdWdfdGQgJT4lCiAgYmluZF90Zl9pZGYodGVybSwgZG9jdW1lbnQsIGNvdW50KSAlPiUgCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpCgppbmF1Z190Zl9pZGYKICAKYGBgCmBgYHtyfQoKaW5hdWdfdGZfaWRmICU+JSAKICBmaWx0ZXIoZG9jdW1lbnQgJWluJSBjKCIxODYxLUxpbmNvbG4iLCAiMTkzMy1Sb29zZXZlbHQiLCAiMTk2MS1LZW5uZWR5IiwgIjIwMDktT2JhbWEiKSkgJT4lIAogIGdncGxvdChhZXMocmVvcmRlcih0ZXJtLHRmX2lkZiksIHRmX2lkZiwgZmlsbCA9IHRmX2lkZikpICsKICBnZW9tX2NvbCgpKwogIGNvb3JkX2ZsaXAoKSsKICBmYWNldF93cmFwKH5kb2N1bWVudCkKYGBgCgpgYGB7cn0KbGlicmFyeSh0aWR5cikKCnllYXJfdGVybV9jb3VudHMgPC0gaW5hdWdfdGQgJT4lIAogIGV4dHJhY3QoZG9jdW1lbnQsICJ5ZWFyIiwgIihcXGQrKSIsIGNvbnZlcnQgPSBUUlVFKSAlPiUgCiAgY29tcGxldGUoeWVhciwgdGVybSwgZmlsbCA9IGxpc3QoY291bnQgPSAwKSkgJT4lIAogIGdyb3VwX2J5KHllYXIpICU+JSAKICBtdXRhdGUoeWVhcl90b3RhbCA9IHN1bShjb3VudCkpCmBgYAoKYGBge3J9CnllYXJfdGVybV9jb3VudHMgJT4lIAogIGZpbHRlcih0ZXJtICVpbiUgYygiZ29kIiwgImFtZXJpY2EiLCAiZm9yZWlnbiIsCiAgICAgICAgICAgICAgICAgICAgICJ1bmlvbiIsICJjb25zdGl0dXRpb24iLCAiZnJlZWRvbSIpKSAlPiUgCiAgZ2dwbG90KGFlcyh5ZWFyLCBjb3VudCAvIHllYXJfdG90YWwpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aCgpICsKICBmYWNldF93cmFwKH50ZXJtLCBzY2FsZXMgPSAiZnJlZV95IikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KCkpICsKICB5bGFiKCIlIGZyZXF1ZW5jeSBvZiB3b3JkIGluIGluYXVndXJhbCBhZGRyZXNzIikKYGBgCgpDYXN0aW5nIHRpZHkgdGV4dCBkYXRhIGludG8gYSBtYXRyaXgKYGBge3J9CmFwX3RkICU+JSAKICBjYXN0X2R0bShkb2N1bWVudCwgdGVybSwgY291bnQpCmBgYAoKYGBge3J9CmxpYnJhcnkoTWF0cml4KQoKbSA8LSBhcF90ZCAlPiUgCiAgY2FzdF9zcGFyc2UoZG9jdW1lbnQsIHRlcm0gLCBjb3VudCkKCgoKY2xhc3MobSkKCmRpbShtKQpgYGAKYGBge3J9CmxpYnJhcnkoamFuZWF1c3RlbnIpCgphdXN0ZW5fZHRtIDwtIGF1c3Rlbl9ib29rcygpICU+JSAKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JSAKICBjb3VudChib29rLCB3b3JkKSAlPiUgCiAgY2FzdF9kdG0oYm9vaywgd29yZCwgbikKCmF1c3Rlbl9kdG0KYGBgCgpUaWR5aW5nIGNvcnB1cyBvYmplY3RzIHdpdGggbWV0YWRhdGEKCmBgYHtyfQpkYXRhKCJhY3EiKQoKYWNxCmBgYApgYGB7cn0KYWNxW1sxXV0KYGBgCgpgYGB7cn0KYWNxX3RkIDwtIHRpZHkoYWNxKQoKYWNxX3RkCmBgYApgYGB7cn0KCmFjcV90b2tlbnMgPC0gYWNxX3RkICU+JSAKICBzZWxlY3QoLXBsYWNlcykgJT4lIAogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lIAogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9ICJ3b3JkIikKCiMgbW9zdCBjb21tb24gd29yZHMKCmFjcV90b2tlbnMgJT4lIAogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQpgYGAKYGBge3J9CgphY3FfdG9rZW5zICU+JSAKICBjb3VudChpZCwgd29yZCkgJT4lIAogIGJpbmRfdGZfaWRmKHdvcmQsIGlkLCBuKSAlPiUgCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpCmBgYAoKIyNFeGFtcGxlOiBNaW5pbmcgRmluYW5jaWFsIEFydGljbGVzCmBgYHtyLCBldmFsPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KHRtLnBsdWdpbi53ZWJtaW5pbmcpCmxpYnJhcnkocHVycnIpCgojIHN1ZG8gbG4gLWYgLXMgJCgvdXNyL2xpYmV4ZWMvamF2YV9ob21lKS9qcmUvbGliL3NlcnZlci9saWJqdm0uZHlsaWIgL3Vzci9sb2NhbC9saWIKCmNvbXBhbnkgPC0gYygiTWljcm9zb2Z0IiwgIkFwcGxlIiwgIkdvb2dsZSIsICJBbWF6b24iKQogICAgICAgICAgICAgCiAgICAgICAgICAgICAjIkZhY2Vib2siLCJUd2l0dGVyIiwgIklCTSIsICJZYWhvbyIsICJOZXRmbGl4IikKCnN5bWJvbCA8LSBjKCJNU0ZUIiwgIkFBUEwiLCAiR09PRyIsICJBTVpOIikKICAgICAgICAgICAgCiAgICAgICAgICAgICMgIkZCIiwgIlRXVFIiLCAiSUJNIiwgIllIT08iLCAiTkZMWCIpCgoKZG93bmxvYWRfYXJ0aWNsZXMgPC0gZnVuY3Rpb24oc3ltYm9sKSB7CiAgV2ViQ29ycHVzKEdvb2dsZUZpbmFuY2VTb3VyY2UocGFzdGUwKCJOQVNEQVE6Iiwgc3ltYm9sKSkpCn0KCnN0b2NrX2FydGljbGVzIDwtIGRhdGFfZnJhbWUoY29tcGFueSA9IGNvbXBhbnksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ltYm9sID0gc3ltYm9sKSAlPiUgCiAgbXV0YXRlKGNvcnB1cyA9IG1hcChzeW1ib2wsIGRvd25sb2FkX2FydGljbGVzKSkKCnN0b2NrX2FydGljbGVzCgpgYGAKCgpgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0Kc3RvY2tfdG9rZW5zIDwtIHN0b2NrX2FydGljbGVzICU+JSAKICB1bm5lc3QobWFwKGNvcnB1cywgdGlkeSkpICU+JSAKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JSAKICBzZWxlY3QoY29tcGFueSwgZGF0ZXRpbWVzdGFtcCwgd29yZCwgaWQsIGhlYWRpbmcpCgpzdG9ja190b2tlbnMKYGBgCmBgYHtyfQpsaWJyYXJ5KHN0cmluZ3IpCgpzdG9ja190Zl9pZGYgPC0gc3RvY2tfdG9rZW5zICU+JSAKICBjb3VudChjb21wYW55LCB3b3JkKSAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZCwgIlxcZCsiKSkgJT4lIAogIGJpbmRfdGZfaWRmKHdvcmQsIGNvbXBhbnksIG4pICU+JSAKICBhcnJhbmdlKC10Zl9pZGYpCiAgCgpzdG9ja190Zl9pZGYKCgpzdG9ja190Zl9pZGYgJT4lIAogIGdyb3VwX2J5KGNvbXBhbnkpICU+JSAKICB0b3BfbigxMCwgdGZfaWRmKSAlPiUgCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JSAKICB1bmdyb3VwKGNvbXBhbnkpICU+JSAKICBtdXRhdGUod29yZCA9IGZhY3Rvcih3b3JkLCBsZXZlbHMgPSByZXYodW5pcXVlKHdvcmQpKSksCiAgICAgICAgIGNvbXBhbnkgPSBhcy5mYWN0b3IoY29tcGFueSkpICU+JSAKICBnZ3Bsb3QoYWVzKHdvcmQsIHRmX2lkZiwgZmlsbCA9IGNvbXBhbnkpKSsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgbGFicyh4ID0gTlVMTCwgeSA9ICJ0Zi1pZGYiKSArCiAgZmFjZXRfd3JhcCh+Y29tcGFueSwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlIikrCiAgY29vcmRfZmxpcCgpCmBgYAoKV29yZHMgd2l0aCB0aGUgbGFyZ2VzdCBjb250cmlidXRpb24gdG8gc2VudGltZW50IHNjb3JlcyBpbiByZWNlbnQgZmluYW5jaWFsIGFydGljbGVzLCBhY2NvcmRpbmcgdG8gdGhlIEFGSU5OIGRpY3Rpb25hcnkuICBUaGUgImNvbnRyaWJ1dGlvbiIgaXMgdGhlIHByb2R1Y3Qgb2YgdGhlIHdvcmQgYW5kIHRoZSBzZW50aW1lbnQgc2NvcmUuCgpgYGB7cn0Kc3RvY2tfdG9rZW5zICU+JSAKICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnkgPSAid29yZCIpICU+JSAKICBjb3VudCh3b3JkLCBpZCwgc29ydCA9IFRSVUUpICU+JSAKICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpLCBieSA9ICJ3b3JkIikgJT4lIAogIGdyb3VwX2J5KHdvcmQpICU+JSAKICBzdW1tYXJpemUoY29udHJpYnV0aW9uID0gc3VtKG4gKiBzY29yZSkpICU+JSAKICB0b3BfbigxMiwgYWJzKGNvbnRyaWJ1dGlvbikpICU+JSAKICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgY29udHJpYnV0aW9uKSkgJT4lIAogIGdncGxvdChhZXMod29yZCwgY29udHJpYnV0aW9uKSkgKwogIGdlb21fY29sKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh5ID0gIkZyZXF1ZW5jeSBvZiB3b3JkICogQUZJTk4gc2NvcmUiKQpgYGAKYGBge3J9CgpzdG9ja190b2tlbnMgJT4lIAogIGNvdW50KHdvcmQpICU+JSAKICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJsb3VnaHJhbiIpLCBieSA9ICJ3b3JkIikgJT4lIAogIGdyb3VwX2J5KHNlbnRpbWVudCkgJT4lIAogIHRvcF9uKDUsIG4pICU+JSAKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUgCiAgZ2dwbG90KGFlcyh3b3JkLCBuKSkgKwogIGdlb21fY29sKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgZmFjZXRfd3JhcCh+c2VudGltZW50LCBzY2FsZXMgPSAiZnJlZSIpICsKICB5bGFiKCJGcmVxdWVuY3kgb2YgdGhpcyB3b3JkIGluIHRoZSByZWNlbnQgZmluYW5jaWFsIGFydGljbGVzIikKICAKYGBgCmBgYHtyfQpzdG9ja19zZW50aW1lbnRfY291bnQgPC0gc3RvY2tfdG9rZW5zICU+JSAKICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJsb3VnaHJhbiIpLCBieSA9ICJ3b3JkIikgJT4lIAogIGNvdW50KHNlbnRpbWVudCwgY29tcGFueSkgJT4lIAogIHNwcmVhZChzZW50aW1lbnQsIG4sIGZpbGwgPSAwKQoKc3RvY2tfc2VudGltZW50X2NvdW50CmBgYAoKYGBge3J9CnN0b2NrX3NlbnRpbWVudF9jb3VudCAlPiUgCiAgbXV0YXRlKHNjb3JlID0gKHBvc2l0aXZlIC0gbmVnYXRpdmUpLyhwb3NpdGl2ZSArIG5lZ2F0aXZlKSkgJT4lIAogIG11dGF0ZShjb21wYW55ID0gcmVvcmRlcihjb21wYW55LCBzY29yZSkpICU+JSAKICBnZ3Bsb3QoYWVzKGNvbXBhbnksIHNjb3JlLCBmaWxsID0gc2NvcmUgPiAwKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBjb29yZF9mbGlwKCkgKwogIGxhYnMoeCA9ICJDb21wYW55IiwKICAgICAgIHkgPSAiUG9zaXRpdml0eSBzY29yZSBhbW9uZCAyMCByZWNlbnQgbmV3cyBhcnRpY2xlcyIpCmBgYAojIENoYXB0ZXIgNiBUb2ljIE1vZGVsaW5nCgoqKlRvcGljIG1vZGVsaW5nKiogbWV0aG9kcyBvZCB1bnN1cGVydmlzZWQgY2xhc3NpZmljYXRpb24gb2YgZG9jdW1lbnRzLCBzaW1pbGFyIHRvIGNsdXN0ZXJpbmcgb24gbnVtZXJpYyBkYXRhLCB3aGljaCBmaW5kcyBuYXR1cmFsIGdyb3VwcyBvZiBpdGVtcyBldmVuIHdoZW4gd2UncmUgbm90IHN1cmUgd2hhdCB3ZSdyZSBsb29raW5nIGZvci4gIAoKCkxhdGVudCBEaXJpY2hsZXQgYWxsb2NhdGlvbiAoTERBKSBwb3B1bGFyIG1ldGhvZHMgZm9yIGZpdHRpbmcgYSB0b3BpYyBtb2RlbC4gIEl0IHRyZWF0cyBlYWNoIGRvY3VtZW50IGFzIGEgbWl4dHVyZSBvZiB0b3BpY3MsIGFuZCBlYWNoIHRvcGljIGFzIGEgbWl4dHVyZSBvZiB3b3Jkcy4gCgpgYGB7cn0KbGlicmFyeSh0b3BpY21vZGVscykKCmRhdGEoIkFzc29jaWF0ZWRQcmVzcyIpCkFzc29jaWF0ZWRQcmVzcwoKYGBgCmBgYHtyfQphcF9sZGEgPC0gTERBKEFzc29jaWF0ZWRQcmVzcywgayA9IDIsIGNvbnRyb2wgPSBsaXN0KHNlZWQgPSAxMjM0KSkKYXBfbGRhCgpgYGAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl0ZXh0KQoKYXBfdG9waWNzIDwtIHRpZHkoYXBfbGRhLCBtYXRyaXggPSAiYmV0YSIpCmFwX3RvcGljcwpgYGAKVGVybXMgdGhhdCBhcmUgbW9zdCBjb21tb24gd2l0aGluIGVhY2ggdG9waWMuCmBgYHtyfQojIGxpYnJhcnkoZ2dwbG90MikKIyBsaWJyYXJ5KGRwbHlyKQoKYXBfdG9wX3Rlcm1zIDwtIGFwX3RvcGljcyAlPiUgCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIHRvcF9uKDEwLCBiZXRhKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBhcnJhbmdlKHRvcGljLCAtYmV0YSkKCmFwX3RvcF90ZXJtcyAlPiUgCiAgbXV0YXRlKHRlcm0gPSByZW9yZGVyKHRlcm0sIGJldGEpKSAlPiUgCiAgZ2dwbG90KGFlcyh0ZXJtLGJldGEsIGZpbGwgPSBmYWN0b3IodG9waWMpKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH50b3BpYywgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCgoKYGBgCmBgYHtyfQojIGxpYnJhcnkodGlkeXIpCgpiZXRhX3NwcmVhZCA8LSBhcF90b3BpY3MgJT4lIAogIG11dGF0ZSh0b3BpYyA9IHBhc3RlMCgidG9waWMiLCB0b3BpYykpICU+JSAKICBzcHJlYWQodG9waWMsIGJldGEpICU+JSAKICBmaWx0ZXIodG9waWMxID4gLjAwMSB8IHRvcGljMiA+IC4wMDEpICU+JSAKICBtdXRhdGUobG9nX3JhdGlvID0gbG9nMih0b3BpYzIvIHRvcGljMSkpCgpiZXRhX3NwcmVhZCAlPiUgCiAgZmlsdGVyKGxvZ19yYXRpbyA+MTAgfCBsb2dfcmF0aW8gPCAtMTApICU+JSAKICBnZ3Bsb3QoYWVzKHJlb3JkZXIodGVybSxsb2dfcmF0aW8pLGxvZ19yYXRpbykpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgY29vcmRfZmxpcCgpICsKICBsYWJzKHRpdGxlID0gIldvcmRzIHdpdGggdGhlIGdyZWF0ZXN0IFxuIGRpZmZlcmVuY2VzIGJldHdlZW4gdHdvIHRvcGljcyIsCiAgICAgICB4ID0gInRlcm0iLAogICAgICAgeSA9ICJMb2cyIHJhdGlvIG9mIGJldGEgaW4gdG9waWMyL3RvcGljMSIpCgpgYGAKCkRvY3VtZW50LVRvcGljIFByb2JhYmlsaXRpZXMKCmBgYHtyfQphcF9kb2N1bWVudHMgPC0gdGlkeShhcF9sZGEsIG1hdHJpeCA9ICJnYW1tYSIpCmFwX2RvY3VtZW50cwpgYGAKCmBgYHtyfQp0aWR5KEFzc29jaWF0ZWRQcmVzcykgJT4lIAogIGZpbHRlcihkb2N1bWVudCA9PSA2KSAlPiUgCiAgYXJyYW5nZShkZXNjKGNvdW50KSkKYGBgCiBFeGFtcGxlOiBUaGUgR3JlYXQgTGlicmFyeSBIZWlzdApgYGB7cn0KdGl0bGVzIDwtIGMoIlR3ZW50eSBUaG91c2FuZCBMZWFndWVzIHVuZGVyIHRoZSBTZWEiLCAiVGhlIFdhciBvZiB0aGUgV29ybGRzIiwKICAgICAgICAgICAgIlByaWRlIGFuZCBQcmVqdWRpY2UiLCAiR3JlYXQgRXhwZWN0YXRpb25zIikKCmxpYnJhcnkoZ3V0ZW5iZXJncikKCmJvb2tzIDwtIGd1dGVuYmVyZ193b3Jrcyh0aXRsZSAlaW4lIHRpdGxlcykgJT4lIAogIGd1dGVuYmVyZ19kb3dubG9hZChtZXRhX2ZpZWxkcyA9ICJ0aXRsZSIpCgpib29rcwoKYGBgCgpgYGB7cn0KbGlicmFyeShzdHJpbmdyKQoKIyBEaXZpZGUgaW50byBkb2N1bWVudHMsIGVhY2ggcmVwcmVzZW50aW5nIG9uZSBjaGFwdGVyCnJlZyA8LSByZWdleCgiXmNoYXB0ZXIiLCBpZ25vcmVfY2FzZSA9IFRSVUUpCgpieV9jaGFwdGVyIDwtIGJvb2tzICU+JSAKICBncm91cF9ieSh0aXRsZSkgJT4lIAogIG11dGF0ZShjaGFwdGVyID0gY3Vtc3VtKHN0cl9kZXRlY3QodGV4dCwgcmVnKSkpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGZpbHRlcihjaGFwdGVyID4gMCkgJT4lIAogIHVuaXRlKGRvY3VtZW50LCB0aXRsZSwgY2hhcHRlcikKCiMgU3BsaXQgaW50byB3b3JkcwpieV9jaGFwdGVyX3dvcmQgPC0gYnlfY2hhcHRlciAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpCgoKIyBGaW5kIGRvY3VtZW50LXdvcmQgY291bnRzCndvcmRfY291bnRzIDwtIGJ5X2NoYXB0ZXJfd29yZCAlPiUgCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JSAKICBjb3VudChkb2N1bWVudCwgd29yZCwgc29ydCA9IFRSVUUpICU+JSAKICB1bmdyb3VwKCkKCndvcmRfY291bnRzCmBgYApMREEgb24gY2hhcHRlcnMKYGBge3J9CmNoYXB0ZXJzX2R0bSA8LSB3b3JkX2NvdW50cyAlPiUgCiAgY2FzdF9kZm0oZG9jdW1lbnQsIHdvcmQsIG4pCgpjaGFwdGVyc19kdG0KYGBgCgpgYGB7cn0KY2hhcHRlcnNfbGRhIDwtIExEQShjaGFwdGVyc19kdG0sIGsgPSA0LCBjb250cm9sID0gbGlzdChzZWVkID0gMTIzNCkpCmNoYXB0ZXJzX2xkYQpgYGAKYGBge3J9CmNoYXB0ZXJfdG9waWNzIDwtIHRpZHkoY2hhcHRlcnNfbGRhLCBtYXRyaXggPSAiYmV0YSIpCmNoYXB0ZXJfdG9waWNzCmBgYAoKYGBge3J9CnRvcF90ZXJtcyA8LSBjaGFwdGVyX3RvcGljcyAlPiUgCiAgZ3JvdXBfYnkodG9waWMpICU+JSAKICB0b3Bfbig1LCBiZXRhKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBhcnJhbmdlKHRvcGljLCAtYmV0YSkKCnRvcF90ZXJtcwpgYGAKCmBgYHtyfQoKdG9wX3Rlcm1zICU+JSAKICBtdXRhdGUodGVybSA9IHJlb3JkZXIodGVybSwgYmV0YSkpICU+JSAKICBnZ3Bsb3QoYWVzKHRlcm0sIGJldGEsIGZpbGwgLSBmYWN0b3IodG9waWMpKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH4gdG9waWMsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKUGVyLURvY3VtZW50IENsYXNzaWZpY2F0aW9uCmBgYHtyfQpjaGFwdGVyc19nYW1tYSA8LSB0aWR5KGNoYXB0ZXJzX2xkYSwgbWF0cml4ID0gImdhbW1hIikKCmNoYXB0ZXJzX2dhbW1hCmBgYAoKYGBge3J9CmNoYXB0ZXJzX2dhbW1hIDwtIGNoYXB0ZXJzX2dhbW1hICU+JSAKICBzZXBhcmF0ZShkb2N1bWVudCwgYygidGl0bGUiLCAiY2hhcHRlciIpLCBzZXAgPSAiXyIsIGNvbnZlcnQgPSBUUlVFKQpjaGFwdGVyc19nYW1tYQoKCiMgUmVvcmRlciB0aXRsZXMgaW4gb3JkZSBvZiB0b3BpYyAxLCB0b3BpYyAyLCBldGMuIGJlZm9yZSBwbG90dGluZwoKY2hhcHRlcnNfZ2FtbWEgJT4lIAogIG11dGF0ZSh0aXRsZSA9IHJlb3JkZXIodGl0bGUsIGdhbW1hICogdG9waWMpKSAlPiUgCiAgZ2dwbG90KGFlcyhmYWN0b3IodG9waWMpLCBnYW1tYSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZmFjZXRfd3JhcCh+dGl0bGUpCmBgYAoKYGBge3J9CmNoYXB0ZXJfY2xhc3NpZmljYXRpb25zIDwtIGNoYXB0ZXJzX2dhbW1hICU+JSAKICBncm91cF9ieSh0aXRsZSwgY2hhcHRlcikgJT4lIAogIHRvcF9uKDEsIGdhbW1hKSAlPiUgCiAgdW5ncm91cCgpCgpjaGFwdGVyX2NsYXNzaWZpY2F0aW9ucwpgYGAKCmBgYHtyfQpib29rX3RvcGljcyA8LSBjaGFwdGVyX2NsYXNzaWZpY2F0aW9ucyAlPiUgCiAgY291bnQodGl0bGUsIHRvcGljKSAlPiUgCiAgZ3JvdXBfYnkodGl0bGUpICU+JSAKICB0b3BfbigxLCBuKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICB0cmFuc211dGUoY29uc2Vuc3VzID0gdGl0bGUsIHRvcGljKQoKY2hhcHRlcl9jbGFzc2lmaWNhdGlvbnMgJT4lIAogIGlubmVyX2pvaW4oYm9va190b3BpY3MsIGJ5ID0gInRvcGljIikgJT4lIAogIGZpbHRlcih0aXRsZSAhPSBjb25zZW5zdXMpCgoKYGBgCkJ5LVdvcmQgQXNzaWdubWVudHM6YXVnbWVudApgYGB7cn0KIyBsaWJyYXJ5KGJyb29tKQojIGFzc2lnbm1lbnRzIDwtIGF1Z21lbnQoY2hhcHRlcnNfbGRhLCBkYXRhID0gY2hhcHRlcnNfZHRtKQojIAojIGFzc2lnbm1lbnRzCiMgICAKIyBhc3NpZ25tZW50cyA8LSBhc3NpZ25tZW50cyAlPiUKIyAgIHNlcGFyYXRlKGRvY3VtZW50LCBjKCJ0aXRsZSIsICJjaGFwdGVyIiksIHNlcCA9ICJfIiwgY29udmVydCA9IFRSVUUpICU+JQojICAgaW5uZXJfam9pbihib29rX3RvcGljcywgYnkgPSBjKCIudG9waWMiID0gInRvcGljIikpCiMgCiMgYXNzaWdubWVudHMKIyAKIyBhc3NpZ25tZW50cyAlPiUKIyAgIGNvdW50KHRpdGxlLCBjb25zZW5zdXMsIHd0ID0gY291bnQpICU+JQojICAgZ3JvdXBfYnkodGl0bGUpICU+JQojICAgbXV0YXRlKHBlcmNlbnQgPSBuIC8gc3VtKG4pKSAlPiUKIyAgIGdncGxvdChhZXMoY29uc2Vuc3VzLCB0aXRsZSwgZmlsbCA9IHBlcmNlbnQpKSArCiMgICBnZW9tX3RpbGUoKSArCiMgICBzY2FsZV9maWxsX2dyYWRpZW50MihoaWdoID0gInJlZCIsIGxhYmVsID0gcGVyY2VudF9mb3JtYXQoKSkgKwojICAgdGhlbWVfbWluaW1hbCgpICsKIyAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSksCiMgICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKSArCiMgICBsYWJzKHggPSAiQm9vayB3b3JkcyB3ZXJlIGFzc2lnbmVkIHRvIiwKIyAgICAgICAgeSA9ICJCb29rIHdvcmRzIGNhbWUgZnJvbSIsCiMgICAgICAgIGZpbGwgPSAiJSBvZiBhc3NpZ25tZW50cyIpCiMgCiMgCiMgCiMgd3Jvbmdfd29yZHMgPC0gYXNzaWdubWVudHMgJT4lCiMgICBmaWx0ZXIodGl0bGUgIT0gY29uc2Vuc3VzKQojIAojIHdyb25nX3dvcmRzCiMgCiMgd3Jvbmdfd29yZHMgJT4lCiMgICBjb3VudCh0aXRsZSwgY29uc2Vuc3VzLCB0ZXJtLCB3dCA9IGNvdW50KSAlPiUKIyAgIHVuZ3JvdXAoKSAlPiUKIyAgIGFycmFuZ2UoZGVzYyhuKSkKIyAKIyB3b3JkX2NvdW50cyAlPiUKIyAgIGZpbHRlcih3b3JkID09ICJmbG9wc29uIikKYGBgCgpBbHRlcm5hdGl2ZSBMREEgSW1wbGVtZW50YXRpb25zCmBgYHtyfQpsaWJyYXJ5KG1hbGxldCkKCiMgY3JlYXRlIGEgdmVjdG9yIHdpdGggb25lIHN0cmluZyBwZXIgY2hhcHRlcgpjb2xsYXBzZWQgPC0gYnlfY2hhcHRlcl93b3JkICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9ICJ3b3JkIikgJT4lCiAgbXV0YXRlKHdvcmQgPSBzdHJfcmVwbGFjZSh3b3JkLCAiJyIsICIiKSkgJT4lCiAgZ3JvdXBfYnkoZG9jdW1lbnQpICU+JQogIHN1bW1hcml6ZSh0ZXh0ID0gcGFzdGUod29yZCwgY29sbGFwc2UgPSAiICIpKQoKIyBjcmVhdGUgYW4gZW1wdHkgZmlsZSBvZiAic3RvcHdvcmRzIgpmaWxlLmNyZWF0ZShlbXB0eV9maWxlIDwtIHRlbXBmaWxlKCkpCmRvY3MgPC0gbWFsbGV0LmltcG9ydChjb2xsYXBzZWQkZG9jdW1lbnQsIGNvbGxhcHNlZCR0ZXh0LCBlbXB0eV9maWxlKQoKbWFsbGV0X21vZGVsIDwtIE1hbGxldExEQShudW0udG9waWNzID0gNCkKbWFsbGV0X21vZGVsJGxvYWREb2N1bWVudHMoZG9jcykKbWFsbGV0X21vZGVsJHRyYWluKDEwMCkKYGBgCgoKYGBge3J9CiMgd29yZC10b3BpYyBwYWlycwp0aWR5KG1hbGxldF9tb2RlbCkKCiMgZG9jdW1lbnQtdG9waWMgcGFpcnMKdGlkeShtYWxsZXRfbW9kZWwsIG1hdHJpeCA9ICJnYW1tYSIpCgojIGNvbHVtbiBuZWVkcyB0byBiZSBuYW1lZCAidGVybSIgZm9yICJhdWdtZW50Igp0ZXJtX2NvdW50cyA8LSByZW5hbWUod29yZF9jb3VudHMsIHRlcm0gPSB3b3JkKQphdWdtZW50KG1hbGxldF9tb2RlbCwgdGVybV9jb3VudHMpCgpgYGAKCkNoYXB0ZXIgNwo=